2012-07-12 61 views
8

我有一個360度旋轉的組合鎖。對應的旋轉對象到數值

組合鎖具有數值,它們都是純粹的圖形。

我需要一種方法將圖像的旋轉轉換爲圖形上的0-99值。

在這第一個圖形,該值應該能夠告訴我,「0」

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

在這種圖形時,使用者旋轉圖像後,該值應該能夠告訴我「 72"

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

下面是代碼:

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); 
         } 
       } 
     } 
} 

我想我需要將矩陣中的某些信息轉換爲0-99的值。

+2

只想說,這些都是美麗的圖形。 – 2012-07-22 16:48:50

回答

8

你應該完全重組你的代碼。一次又一次地將新旋轉倍增到矩陣中是一種數值不穩定的計算。最終位圖將變形。嘗試從矩陣中檢索旋轉角度太複雜且不必要。

首先請注意,this是一個有用的關於繪製位圖旋轉關於選定點的有用的文章。

只需保持一個單一的double dialAngle = 0即錶盤的當前旋轉角度。

您正在做太多的工作來從觸摸位置檢索角度。假設(x0,y0)是觸摸開始的位置。那時候,

// 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); 

這是起始角度。當觸摸拖動到(x,y)時,使用此座標相對於初始觸摸調整撥號。然後更新矩陣和重繪:

// 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. 

Math.atan2(y, x)做所有的你與象限和arcsines做什麼。

爲了得到當前角度的「滴答」,你需要2個PI弧度對應於100,所以它是非常簡單的:

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

要查找實際的最近的刻度爲整數,輪分數和mod 100.注意你可以忽略矩陣!

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

這將始終工作,因爲dialAngle是[0..2pi)。爲了將100的四捨五入映射回0,需要mod。

+0

基因是對的。你不應該積累到一個變換矩陣。獲取用戶輸入,將其累加到「dialRotation」值中,並且每次都計算新的旋轉矩陣。 – 2012-07-22 16:59:32

4

這應該是一個簡單的乘法與您的程度值(0-359),按比例縮小到0-99的規模「規模」因素:

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

編輯:糾正getAngle功能

對於getAngle,您可以使用atan2函數,該函數將笛卡爾座標轉換爲角度。

只是存儲在觸摸第一觸摸座標下來,上舉,你可以採用以下的計算:

  // 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); 

的弧度有一個範圍內的兩個圓周率。模數計算旋轉它,所以0的值指向上。旋轉方向是逆時針方向。

所以你需要將它轉換成度並改變旋轉方向以獲得正確的角度。

+0

非常酷,我的MotionEvent.Action_Move情況下生成的變量有問題,這個問題會稍微解決,翻譯一下,你能看看嗎? – CQM 2012-07-12 21:14:56

+1

@CQM我通過從我的代碼庫中複製粘貼了一些與您需要的東西非常相似的東西來更新我的評論。您可能需要調整它,因爲我沒有驗證背後的數學。 – tiguchi 2012-07-12 21:37:12

5

爲了更好地理解矩陣的作用,理解2D圖形變換矩陣很有幫助:http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics。如果你正在做的只是旋轉(不是說,轉換或縮放),提取旋轉相對容易。但是,更實際,你可以修改輪換代碼,並存儲可變

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); 
    } 

的狀態保持一個變量來保存度的總轉動,這在您的旋轉功能增加。現在,我們知道3.6度是一個勾號。簡單的數學產量

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

你必須檢查的最後一兩件事:如果你有正是 360度的旋轉,或100剔號,你必須把它當作0(因爲沒有勾號100)

+0

這裏有一些問題,我認爲它與rotateDialer實際採用的變量有關。您可以在MotionEvent.Action_Move中查看此變量 – CQM 2012-07-12 21:04:03

+0

rotateDialer接受的變量是角度的變化,這是代碼你表示計算。這就是爲什麼我們存儲變量rotationDegrees:人員撥號可以移動+90度,然後-180,讓我們在-90。但有一點可能是錯誤的,那就是正向旋轉是逆時針的,在這種情況下,if(tickNumber> 0)tickNumber = 100 - tickNumber'。 – 2012-07-16 15:05:49

2

錶盤應正好旋轉3.6度,以便從一個標記轉到下一個或上一個標記。 每次用戶的觸摸旋轉(圍繞中心)3.6度時,錶盤應旋轉1個標記(3.6度)。

代碼片段:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle()計算用戶的觸摸點WRT的角度使用atan2撥打中心。
  • setDialMark通過更改標記數來旋轉撥號盤。

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

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