2012-07-12 31 views
8

Tôi có khóa kết hợp xoay vòng tròn 360 độ.Đối tượng được xoay tương ứng với các giá trị số

Khóa kết hợp có giá trị số trên đó, đây hoàn toàn là đồ họa.

Tôi cần cách dịch vòng xoay của hình ảnh thành giá trị 0-99 trên đồ họa.

Trong đồ họa đầu tiên này, giá trị sẽ có thể cho tôi biết "0"

http://i48.tinypic.com/27y67b7.png

Trong đồ họa này, sau khi người dùng đã xoay hình ảnh, giá trị sẽ có thể cho tôi biết " 72"

http://i46.tinypic.com/2ueiogh.png

đây là mã:

package co.sts.combinationlock; 

import android.os.Bundle; 
import android.app.Activity; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Matrix; 
import android.util.Log; 
import android.view.GestureDetector; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.GestureDetector.SimpleOnGestureListener; 
import android.view.View.OnTouchListener; 
import android.view.ViewTreeObserver.OnGlobalLayoutListener; 
import android.widget.ImageView; 
import android.support.v4.app.NavUtils; 

public class ComboLock extends Activity{ 

     private static Bitmap imageOriginal, imageScaled; 
     private static Matrix matrix; 

     private ImageView dialer; 
     private int dialerHeight, dialerWidth; 

     private GestureDetector detector; 

     // needed for detecting the inversed rotations 
     private boolean[] quadrantTouched; 

     private boolean allowRotating; 

     @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_combo_lock); 

     // load the image only once 
     if (imageOriginal == null) { 
       imageOriginal = BitmapFactory.decodeResource(getResources(), R.drawable.numbers); 
     } 

     // initialize the matrix only once 
     if (matrix == null) { 
       matrix = new Matrix(); 
     } else { 
       // not needed, you can also post the matrix immediately to restore the old state 
       matrix.reset(); 
     } 

     detector = new GestureDetector(this, new MyGestureDetector()); 

     // there is no 0th quadrant, to keep it simple the first value gets ignored 
     quadrantTouched = new boolean[] { false, false, false, false, false }; 

     allowRotating = true; 

     dialer = (ImageView) findViewById(R.id.locknumbers); 
     dialer.setOnTouchListener(new MyOnTouchListener()); 
     dialer.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 

       @Override 
         public void onGlobalLayout() { 
         // method called more than once, but the values only need to be initialized one time 
         if (dialerHeight == 0 || dialerWidth == 0) { 
           dialerHeight = dialer.getHeight(); 
           dialerWidth = dialer.getWidth(); 

           // resize 
             Matrix resize = new Matrix(); 
             //resize.postScale((float)Math.min(dialerWidth, dialerHeight)/(float)imageOriginal.getWidth(), (float)Math.min(dialerWidth, dialerHeight)/(float)imageOriginal.getHeight()); 
             imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false); 

             // translate to the image view's center 
             float translateX = dialerWidth/2 - imageScaled.getWidth()/2; 
             float translateY = dialerHeight/2 - imageScaled.getHeight()/2; 
             matrix.postTranslate(translateX, translateY); 

             dialer.setImageBitmap(imageScaled); 
             dialer.setImageMatrix(matrix); 
         } 
         } 
       }); 

    } 

     /** 
     * Rotate the dialer. 
     * 
     * @param degrees The degrees, the dialer should get rotated. 
     */ 
     private void rotateDialer(float degrees) { 
       matrix.postRotate(degrees, dialerWidth/2, dialerHeight/2); 

       //need to print degrees 

       dialer.setImageMatrix(matrix); 
     } 

     /** 
     * @return The angle of the unit circle with the image view's center 
     */ 
     private double getAngle(double xTouch, double yTouch) { 
       double x = xTouch - (dialerWidth/2d); 
       double y = dialerHeight - yTouch - (dialerHeight/2d); 

       switch (getQuadrant(x, y)) { 
         case 1: 
           return Math.asin(y/Math.hypot(x, y)) * 180/Math.PI; 

         case 2: 
         case 3: 
           return 180 - (Math.asin(y/Math.hypot(x, y)) * 180/Math.PI); 

         case 4: 
           return 360 + Math.asin(y/Math.hypot(x, y)) * 180/Math.PI; 

         default: 
           // ignore, does not happen 
           return 0; 
       } 
     } 

     /** 
     * @return The selected quadrant. 
     */ 
     private static int getQuadrant(double x, double y) { 
       if (x >= 0) { 
         return y >= 0 ? 1 : 4; 
       } else { 
         return y >= 0 ? 2 : 3; 
       } 
     } 

     /** 
     * Simple implementation of an {@link OnTouchListener} for registering the dialer's touch events. 
     */ 
     private class MyOnTouchListener implements OnTouchListener { 

       private double startAngle; 

       @Override 
       public boolean onTouch(View v, MotionEvent event) { 

         switch (event.getAction()) { 

           case MotionEvent.ACTION_DOWN: 

             // reset the touched quadrants 
             for (int i = 0; i < quadrantTouched.length; i++) { 
               quadrantTouched[i] = false; 
             } 

             allowRotating = false; 

             startAngle = getAngle(event.getX(), event.getY()); 
             break; 

           case MotionEvent.ACTION_MOVE: 
             double currentAngle = getAngle(event.getX(), event.getY()); 
             rotateDialer((float) (startAngle - currentAngle)); 
             startAngle = currentAngle; 
             break; 

           case MotionEvent.ACTION_UP: 
             allowRotating = true; 
             break; 
         } 

         // set the touched quadrant to true 
         quadrantTouched[getQuadrant(event.getX() - (dialerWidth/2), dialerHeight - event.getY() - (dialerHeight/2))] = true; 

         detector.onTouchEvent(event); 

         return true; 
       } 
     } 

     /** 
     * Simple implementation of a {@link SimpleOnGestureListener} for detecting a fling event. 
     */ 
     private class MyGestureDetector extends SimpleOnGestureListener { 
       @Override 
       public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 

         // get the quadrant of the start and the end of the fling 
         int q1 = getQuadrant(e1.getX() - (dialerWidth/2), dialerHeight - e1.getY() - (dialerHeight/2)); 
         int q2 = getQuadrant(e2.getX() - (dialerWidth/2), dialerHeight - e2.getY() - (dialerHeight/2)); 

         // the inversed rotations 
         if ((q1 == 2 && q2 == 2 && Math.abs(velocityX) < Math.abs(velocityY)) 
             || (q1 == 3 && q2 == 3) 
             || (q1 == 1 && q2 == 3) 
             || (q1 == 4 && q2 == 4 && Math.abs(velocityX) > Math.abs(velocityY)) 
             || ((q1 == 2 && q2 == 3) || (q1 == 3 && q2 == 2)) 
             || ((q1 == 3 && q2 == 4) || (q1 == 4 && q2 == 3)) 
             || (q1 == 2 && q2 == 4 && quadrantTouched[3]) 
             || (q1 == 4 && q2 == 2 && quadrantTouched[3])) { 

           dialer.post(new FlingRunnable(-1 * (velocityX + velocityY))); 
         } else { 
           // the normal rotation 
           dialer.post(new FlingRunnable(velocityX + velocityY)); 
         } 

         return true; 
       } 
     } 

     /** 
     * A {@link Runnable} for animating the the dialer's fling. 
     */ 
     private class FlingRunnable implements Runnable { 

       private float velocity; 

       public FlingRunnable(float velocity) { 
         this.velocity = velocity; 
       } 

       @Override 
       public void run() { 
         if (Math.abs(velocity) > 5 && allowRotating) { 
           //rotateDialer(velocity/75); 
           //velocity /= 1.0666F; 

           // post this instance again 
           dialer.post(this); 
         } 
       } 
     } 
} 

Tôi nghĩ rằng tôi cần dịch một số thông tin từ ma trận sang giá trị 0-99.

+2

Chỉ muốn nói, đó là đồ họa đẹp. –

Trả lời

8

Bạn nên tổ chức lại mã hoàn toàn. Việc nhân các phép quay mới sau nhiều lần thành ma trận lặp đi lặp lại là một phép tính không ổn định về số lượng. Cuối cùng bitmap sẽ bị biến dạng. Cố gắng lấy lại góc quay từ ma trận quá phức tạp và không cần thiết.

Lưu ý đầu tiên là this là một bài viết hữu ích trước khi vẽ bitmap với vòng xoay về điểm đã chọn.

Chỉ cần duy trì double dialAngle = 0 là góc quay hiện tại của mặt số.

Bạn đang thực hiện quá nhiều công việc để lấy góc từ vị trí cảm ứng. Để (x0,y0) là vị trí bắt đầu chạm. Vào thời điểm đó,

// Record the angle at initial touch for use in dragging. 
dialAngleAtTouch = dialAngle; 
// Find angle from x-axis made by initial touch coordinate. 
// y-coordinate might need to be negated due to y=0 -> screen top. 
// This will be obvious during testing. 
a0 = Math.atan2(y0 - yDialCenter, x0 - xDialCenter); 

Đây là góc bắt đầu. Khi chạm kéo đến (x,y), hãy sử dụng tọa độ này để điều chỉnh quay số liên quan đến lần chạm đầu tiên. Sau đó cập nhật ma trận và vẽ lại:

// Find new angle to x-axis. Same comment as above on y coord. 
a = Math.atan2(y - yDialCenter, x - xDialCenter); 
// New dial angle is offset from the one at initial touch. 
dialAngle = dialAngleAtTouch + (a - a0); 
// normalize angles to the interval [0..2pi) 
while (dialAngle < 0) dialAngle += 2 * Math.PI; 
while (dialAngle >= 2 * Math.PI) dialAngle -= 2 * Math.PI; 

// Set the matrix for every frame drawn. Matrix API has a call 
// for rotation about a point. Use it! 
matrix.setRotate((float)dialAngle * (180/3.1415926f), xDialCenter, yDialCenter); 

// Invalidate the view now so it's redrawn in with the new matrix value. 

Lưu ý Math.atan2(y, x) thực hiện tất cả những gì bạn đang làm với phần tư và arcsines.

Để có được "đánh dấu" của góc hiện tại, bạn cần 2 radian pi tương ứng với 100, do đó, nó rất đơn giản:

double fractionalTick = dialAngle/(2 * Math.Pi) * 100; 

Để tìm đánh dấu khu vực gần thực tế là một số nguyên, tròn phần và sửa đổi 100. Lưu ý bạn có thể bỏ qua ma trận!

int tick = (int)(fractionalTick + 0.5) % 100; 

Điều này sẽ luôn hoạt động vì dialAngle nằm trong [0..2pi). Cần phải điều chỉnh bản đồ giá trị tròn 100 trở về 0.

+0

Gene là đúng. Bạn không nên tích lũy thành ma trận biến đổi.Lấy đầu vào của người dùng, tích lũy nó vào một giá trị "dialRotation" và tính toán các ma trận quay tươi từ đó mỗi lần. –

4

này phải là một phép nhân đơn giản với một yếu tố "quy mô" mà quy mô xuống giá trị độ của bạn (0-359) để 0-99 quy mô của bạn:

float factor = 99f/359f; 
float scaled = rotationDegree * factor; 

EDIT: Sửa chữa các getAngle chức năng

Đối với getAngle, bạn có thể sử dụng hàm atan2 để thay đổi tọa độ Descartes thành một góc.

Chỉ cần lưu trữ các liên lạc đầu tiên phối hợp trên cảm ứng xuống và trên di chuyển bạn có thể áp dụng cách tính như sau:

  // PointF a = touch start point 
      // PointF b = current touch move point 

      // Translate to origin: 
      float x = b.x - a.x; 
      float y = b.y - a.y; 

      float radians = (float) ((Math.atan2(-y, x) + Math.PI + HALF_PI) % TWO_PI); 

Các radian có tầm hoạt động hai pi. các phép tính modulo xoay nó thành giá trị 0 điểm. Hướng quay ngược chiều kim đồng hồ.

Vì vậy, bạn cần phải chuyển đổi thành độ và thay đổi hướng quay để nhận được góc chính xác.

+0

rất tuyệt, có điều gì đó không ổn với các biến được tạo trong trường hợp MotionEvent.Action_Move của tôi, vít này lên và dịch một chút, bạn có thể xem nó không? – CQM

+1

@CQM Tôi đã cập nhật nhận xét của mình bằng cách sao chép-dán nội dung nào đó từ cơ sở mã của tôi khá giống với những gì bạn cần. Có thể bạn cần phải điều chỉnh nó, bởi vì tôi không xác minh toán học đằng sau. – tiguchi

5

Để hiểu rõ hơn về ma trận làm gì, sẽ rất hữu ích khi hiểu các ma trận chuyển đổi đồ họa 2d: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics. Nếu điều duy nhất mà bạn đang làm là xoay (không, nói, chuyển đổi hoặc mở rộng quy mô) thì tương đối dễ dàng để trích xuất vòng quay. Nhưng, thực tế hơn, bạn có thể sửa đổi mã xoay vòng và lưu trữ biến trạng thái

private float rotationDegrees = 0; 

    /** 
    * Rotate the dialer. 
    * 
    * @param degrees The degrees, the dialer should get rotated. 
    */ 
    private void rotateDialer(float degrees) 
      matrix.postRotate(degrees, dialerWidth/2, dialerHeight/2); 

      this.rotationDegrees += degrees; 

      // Make sure we don't go over 360 
      this.rotationDegrees = this.rotationDegrees % 360 

      dialer.setImageMatrix(matrix); 
    } 

Giữ biến để lưu tổng quay theo độ, bạn tăng theo chức năng xoay của mình. Bây giờ, chúng ta biết 3,6 độ là một dấu tích. sản lượng toán học đơn giản

tickNumber = (int)rotation*100/360 
// It could be negative 
if (tickNumber < 0) 
    tickNumber = 100 - tickNumber 

Điều cuối cùng mà bạn phải kiểm tra: Nếu bạn có vòng xoay cảng gồm chính xác 360 độ, hoặc một số đánh dấu 100, bạn cần phải đối xử với nó như 0 (vì không no tick 100)

+0

có một số vấn đề với điều này, tôi nghĩ rằng nó đã làm với các biến mà rotateDialer thực sự tham gia. Bạn có thể nhìn vào điều này trong MotionEvent.Action_Move – CQM

+0

Biến mà rotateDialer chấp nhận là một sự thay đổi về góc, đó là những gì mã bạn chỉ ra tính toán. Đó là lý do tại sao chúng tôi lưu trữ một biến xoayDegrees: quay số người có thể di chuyển +90 độ và sau đó -180, để lại cho chúng tôi tại -90. Tuy nhiên, một điều có thể sai, đó là vòng quay dương là ngược chiều kim đồng hồ, trong trường hợp đó, nếu (nếu tickNumber> 0) tickNumber = 100 - tickNumber' thay thế. –

2

Quay số phải được xoay chính xác 3,6 độ để đi từ điểm này sang điểm tiếp theo hoặc trước đó. Mỗi lần chạm của người dùng quay (xung quanh tâm) bằng 3,6 độ, quay số phải được xoay 1 điểm (3,6 độ).

Đoạn mã:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() tính toán góc của điểm wrt touch của người dùng để quay số trung tâm sử dụng atan2.
  • setDialMark quay số theo số điểm đã thay đổi.

.

void setDialMark(int mark) { 
    rotateDialBy(mCurrentMark - mark); 
    mCurrentMark = mark;  
} 

void rotateDialBy(int rotateMarks) { 
    rotationAngle = rotateMarks * 3.6; 
    ... 
    /* Rotate the dial by rotationAngle. */ 
}