Objeto girado correspondiente a valores numéricos

Tengo una cerradura de combinación girando en un círculo de 360 ​​grados.

La cerradura de combinación tiene valores numéricos en él, éstos son puramente gráficos.

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

En este primer gráfico, el valor debería 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 debería 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 alguna información de la matriz a un valor 0-99.

Debe reorganizar su código completamente. Post-multiplicar nuevas rotaciones en 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, note que este es un útil artículo anterior sobre el dibujo de mapas de bits con rotación sobre un punto elegido.

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

Usted está haciendo mucho trabajo para recuperar el ángulo de la ubicación de toque. Sea (x0,y0) el lugar donde se inicia el toque. En ese tiempo,

 // 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 inicial. Cuando el toque se arrastra a (x,y) , utilice esta coordenada para ajustar el dial con respecto al toque inicial. A continuación, actualice la matriz y vuelva 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 cuadrantes y arcos.

Para obtener la "marca" del ángulo actual, se necesitan 2 pi radianes para corresponder a 100, por lo que es muy simple:

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

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

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

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

Para entender mejor lo que hace la matriz, es útil entender las matrices de transformación de gráficos 2d: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics . Si lo único que está haciendo es girar (no, por ejemplo, transformar o escalar) es relativamente fácil extraer la rotación. Pero, más prácticamente, puede 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); } 

Mantenga una variable para almacenar la rotación total en grados, que se incrementa en su función de rotación. Ahora, sabemos que 3.6 grados es una garrapata. Rendimientos matemáticos simples

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

La última cosa que usted tiene que comprobar para: Si usted tiene una rotación de exactamente 360 grados, o un número de la señal 100, usted tiene que tratarlo como 0 (puesto que no hay ninguna señal 100)

Esto debe ser una simple multiplicación con un factor de "escala" que reduce el valor de su grado (0-359) a su escala 0-99:

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

EDIT: Corregir la función getAngle

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

Simplemente guarde la primera coordenada táctil al tocar hacia abajo y en movimiento puede aplicar el siguiente cálculo:

  // PointF a = touch start point // PointF b = current touch move point // Translate to origin: float x = bx - ax; float y = by - ay; float radians = (float) ((Math.atan2(-y, x) + Math.PI + HALF_PI) % TWO_PI); 

Los radianes tienen una gama de dos pi. Los cálculos de módulo lo giran de modo que un valor de 0 puntos arriba. La dirección de rotación es a la izquierda.

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

El dial debe girar exactamente 3,6 grados para ir de una marca a la siguiente o anterior. Cada vez que el toque del usuario gire (alrededor del centro) en 3,6 grados, el dial debe girar 1 punto (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 de contacto del usuario con wrt 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. */ } 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.