2012-07-12 15 views
8

Tengo un bloqueo de combinación que gira en un círculo de 360 ​​grados.Objeto girado correspondiente a valores numéricos

El bloqueo de combinación tiene valores numéricos, estos son puramente gráficos.

Necesito una forma de traducir la rotación de la imagen a los valores de 0-99 en el gráfico.

En este primer gráfico, el valor debe ser capaz de decirme "0"

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

En este gráfico, después de que el usuario ha girado la imagen, el valor debe ser capaz de decirme " 72"

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

Aquí está el código:

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

Creo que necesito traducir parte de la información de la matriz a un valor 0-99.

+2

Solo quería decir que esos son hermosos gráficos. –

Respuesta

8

Debe reorganizar su código por completo. Multiplicar nuevas rotaciones a una matriz una y otra vez es un cálculo numéricamente inestable. Eventualmente, el mapa de bits se distorsionará. Tratar de recuperar el ángulo de rotación de la matriz es demasiado complejo e innecesario.

Primero tenga en cuenta que this es un artículo previo útil sobre el dibujo de mapas de bits con rotación sobre un punto elegido.

Simplemente mantenga un solo double dialAngle = 0 que es el ángulo de rotación actual del dial.

Usted está haciendo demasiado trabajo para recuperar el ángulo de la ubicación táctil. Deje (x0,y0) ser la ubicación donde comienza el toque. En ese momento,

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

Este es el ángulo de inicio. Cuando el toque se arrastra a (x,y), use esta coordenada para ajustar el cuadrante con respecto al toque inicial. A continuación, actualice la matriz y volver a dibujar:

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

Nota Math.atan2(y, x) hace todo lo que está haciendo con los cuadrantes y arcsines.

para obtener el "tick" del ángulo actual, se necesitan 2 pi radianes para corresponder a 100, por lo que es muy sencillo:

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

Para encontrar la señal más cercana real como un entero, alrededor de la fracción y mod por 100. ¡Nota que puedes ignorar la matriz!

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

Esto siempre funciona porque está en dialAngle [0..2pi). El mod es necesario para asignar un valor redondeado de 100 a 0.

+0

Gene tiene razón. No deberías estar acumulando en una matriz de transformación.Tome la entrada del usuario, acumúlela en un valor "dialRotation" y calcule nuevas matrices de rotación cada vez. –

4

Esto debería ser una simple multiplicación con un factor de "escala" que escala abajo de su valor en grados (0-359) a su escala de 0-99:

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

EDIT: La corrección de la función getAngle

Para getAngle, podría usar la función atan2, que transforma las coordenadas cartesianas en un ángulo.

tienda sólo coordinar el primer toque en toque abajo y en la jugada se puede aplicar el siguiente cálculo:

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

Los radianes tienen un rango de dos pi. los cálculos del módulo lo rotan así que un valor de 0 puntos arriba. La dirección de rotación es en sentido contrario a las agujas del reloj.

Así que necesitaría convertir eso en grados y cambiar la dirección de rotación para obtener el ángulo correcto.

+0

muy bien, hay algo mal con las variables generadas en mi caso MotionEvent.Action_Move, esto se estropea y la traducción un poco, ¿puedes mirar eso? – CQM

+1

@CQM Actualicé mi comentario al copiar y pegar algo de mi base de código que es bastante similar a lo que necesita. Es posible que deba ajustarlo, porque no verifiqué las matemáticas. – tiguchi

5

Para comprender mejor lo que hace la matriz, es útil comprender 2d matrices de transformación de gráficos: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics. Si lo único que está haciendo es rotar (no, por ejemplo, transformar o escalar) es relativamente fácil extraer la rotación. Pero, de manera más práctica, es posible modificar el código de rotación, y almacenar una variable de estado

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

Mantener una variable para almacenar la rotación total de en grados, que se incremento en su función de rotación. Ahora, sabemos que 3.6 grados es un tic. rendimientos matemáticos sencillos

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

La última cosa que usted tiene que comprobar: si tiene una rotación de exactamente 360 grados, o un número de garrapatas de 100, hay que tratarlo como 0 (ya que no hay no marcar 100)

+0

hay algunos problemas con esto, creo que tiene que ver con las variables que rotateDialer está tomando en realidad. ¿Puedes ver esto en MotionEvent.Action_Move – CQM

+0

La variable que rotateDialer acepta es un cambio en el ángulo, que es lo que el código usted indicó calcula. Es por eso que almacenamos una rotación variable Grados: la persona que marca puede moverse +90 grados y luego -180, dejándonos en -90. Una cosa que puede estar equivocada, sin embargo, es que la rotación positiva es en sentido antihorario, en cuyo caso 'if (tickNumber> 0) tickNumber = 100 - tickNumber' en su lugar. –

2

El dial debe girarse exactamente 3.6 grados para pasar de una marca a la siguiente o anterior. Cada vez que el toque del usuario gira (alrededor del centro) en 3.6 grados, el dial debe girarse en 1 marca (3.6 grados).

Fragmento de código:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() calcula el ángulo del punto WRT contacto del usuario para marcar el centro usando atan2.
  • setDialMark gira el dial por el número de marcas cambiadas.

.

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

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