2012-07-12 22 views
8

私は360度の円で回転するコンビネーションロックを持っています。対応する回転オブジェクトと数値

コンビネーションロックに数値があります。これらは純粋にグラフィカルです。

イメージの回転をグラフィック上の0〜99の値に変換する方法が必要です。

この最初のグラフィックでは、値が「0」を私に伝えることができるはずです。このグラフィックで

http://i48.tinypic.com/27y67b7.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); 
         } 
       } 
     } 
} 
:ここ72"

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

コードです

私は行列の情報を0から99の値に変換する必要があると思います。

+2

これは美しいグラフィックです。 –

答えて

8

コードを完全に再編成する必要があります。何度も行列に新しい回転を後で乗算することは、数値的に不安定な計算です。最終的にビットマップが歪んでしまいます。行列から回転角を取得しようとする試みは、あまりにも複雑で不要です。

最初の注:thisは、選択した点を中心に回転するビットマップを描画するのに便利な先行記事です。

ダイヤルの現在の回転角度であるdouble dialAngle = 0を1つだけ維持してください。

タッチ位置から角度を取得するにはあまりにも多くの作業を行っています。タッチが始まる場所を(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でやっていることのすべてを行います。

現在の角度の「目盛り」を取得するには、100に対応するように2パイラジアンを必要とするので、それは非常に簡単です:

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

整数として実際の最寄りのダニを見つけるには、端数を丸めますあなたは行列を無視することができます!

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

dialAngleは[0..2pi]であるため、これは常に有効です。モジュレーションは、丸められた値100を0に戻すために必要です。

+0

遺伝子が正しい。あなたは変換マトリックスに蓄積してはいけません。ユーザの入力を受け取り、それを「dialRotation」値に累積し、その都度新しい回転行列を計算します。 –

4

これはあなたの0-99スケールにご度の値をスケールダウン「スケール」係数(0-359)との単純な乗算する必要があります:

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

はEDIT:

getAngle機能を修正

getAngleの代わりに、atan2関数を使用すると、デカルト座標を角度に変換できます。

ただ、最初のタッチがタッチダウン上の座標格納して移動して、あなたは、次の計算に適用することができます:ラジアンは2つのパイの範囲を持っている

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

+1

@CQM私はあなたが必要とするものにかなり似ているコードベースから何かをコピー貼り付けてコメントを更新しました。私は後ろの数学を検証しなかったので、調整が必要な可能性があります。 – tiguchi

5

マトリックスが何をするのかを理解するには、2次元グラフィックス変換マトリックス: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が実際に取り込んでいる変数と関係があると思います。 – CQM

+0

rotateDialerが受け入れる変数は、角度の変化ですあなたは計算を指示しました。だから私たちは変数rotationDegreesを保存しています。ダイヤルする人は+90度、次に-180度移動でき、-90のままにします。しかし、間違っているかもしれない1つのことは、正のローテーションが反時計回りであることです。その場合はif(tickNumber> 0)tickNumber = 100-tickNumberです。 –

2

ダイヤルは、あるマークから次のまたは前のマークに移動するためには、正確に3.6度回転する必要があります。 ユーザーのタッチが(中心を中心に)3.6度回転するたびに、ダイヤルを1マーク(3.6度)回転させる必要があります。

コードスニペット:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle()atan2を使用してセンターをダイヤルするユーザのタッチポイントWRTの角度を計算します。
  • setDialMarkは、マークの数を変更してダイヤルを回転します。

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

void rotateDialBy(int rotateMarks) { 
    rotationAngle = rotateMarks * 3.6; 
    ... 
    /* Rotate the dial by rotationAngle. */ 
} 
関連する問題