2012-07-12 15 views
8

Ho una serratura a combinazione che ruota in un cerchio di 360 gradi.Oggetto ruotato corrispondente a valori numerici

Il lucchetto a combinazione ha valori numerici, questi sono puramente grafici.

Ho bisogno di un modo per tradurre la rotazione dell'immagine ai valori 0-99 sul grafico.

In questo primo grafico, il valore dovrebbe essere in grado di dirmi "0"

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

In questa grafica, dopo che l'utente ha ruotato l'immagine, il valore dovrebbe essere in grado di dirmi " 72"

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

Ecco il codice:

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

Penso di aver bisogno di tradurre alcune informazioni dalla matrice in un valore 0-99.

+2

Volevo solo dirlo, quelle sono belle grafica. –

risposta

8

È necessario riorganizzare completamente il codice. Le nuove rotazioni successive alla moltiplicazione in una matrice ripetutamente sono un calcolo numericamente instabile. Alla fine la bitmap diventerà distorta. Cercare di recuperare l'angolo di rotazione dalla matrice è troppo complesso e non necessario.

Innanzitutto, lo this è un utile articolo precedente sul disegno di bitmap con rotazione attorno a un punto scelto.

Basta mantenere un singolo double dialAngle = 0 che corrisponde all'angolo di rotazione corrente del quadrante.

Stai facendo troppo lavoro per recuperare l'angolo dal punto di contatto. Lascia che sia (x0,y0) il luogo in cui inizia il tocco. A quel tempo,

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

Questo è l'angolo iniziale. Quando il tocco viene trascinato su (x,y), utilizzare questa coordinata per regolare la ghiera rispetto al tocco iniziale. Quindi aggiornare la matrice e ridisegnare:

// 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) fa tutto quello che stai facendo con i quadranti e arcsines.

per ottenere il "tick" del angolo corrente, avete bisogno di 2 radianti pi corrispondere a 100, quindi è molto semplice:

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

Per trovare il segno di spunta vicino attuale come un intero, rotonda della frazione e mod per 100. Nota puoi ignorare la matrice!

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

Questo sarà sempre funziona perché dialAngle è in [0..2pi). Il mod è necessario per mappare un valore arrotondato di 100 a 0.

+0

Gene ha ragione. Non dovresti accumularti in una matrice di trasformazione.Prendi l'input dell'utente, accumulalo in un valore "dialRotation" e calcola sempre nuove matrici di rotazione. –

4

Questa dovrebbe essere una semplice moltiplicazione con un fattore di "scala" in grado di scalare verso il basso il valore della laurea (0-359) alla scala 0-99:

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

EDIT: Correzione la funzione getAngle

Per getAngle è invece possibile utilizzare la funzione atan2, che trasforma le coordinate cartesiane in un angolo.

Basta memorizzare la coordinata primo tocco sul touch down e in movimento è possibile applicare il seguente calcolo:

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

I radianti hanno una gamma di due pi greco. i calcoli modulo ruotarlo in modo che un valore di 0 punti in su. La direzione di rotazione è antioraria.

Quindi è necessario convertirlo in gradi e modificare il senso di rotazione per ottenere l'angolo corretto.

+0

molto interessante, c'è qualcosa di sbagliato nelle variabili generate nel mio caso MotionEvent.Action_Move, questo rovina e la traduzione un po ', puoi guardarlo? – CQM

+1

@CQM Ho aggiornato il mio commento incollando qualcosa dal mio codice base che è molto simile a quello che ti serve. È possibile che tu debba regolarlo, perché non ho verificato la matematica dietro. – tiguchi

5

Per capire meglio cosa fa la matrice, è utile comprendere le matrici di trasformazione grafica 2D: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics. Se l'unica cosa che stai facendo è ruotare (non, per esempio, trasformare o ridimensionare) è relativamente facile estrarre la rotazione. Ma, più praticamente, si può modificare il codice di rotazione, e memorizzare una variabile di stato

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

Mantenere una variabile per memorizzare la rotazione totale in gradi, che si incrementa nella funzione di rotazione. Ora, sappiamo 3.6 gradi è un segno di spunta. Semplici rendimenti matematica

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

L'ultima cosa che dovete controllare: Se si dispone di una rotazione di esattamente 360 gradi, o un numero tick di 100, è necessario trattarlo come 0 (poiché non v'è no tick 100)

+0

ci sono alcuni problemi con questo, penso che abbia a che fare con le variabili che rotateDialer sta effettivamente prendendo. Puoi guardarlo in MotionEvent.Action_Move – CQM

+0

La variabile che rotateDialer accetta è un cambiamento di angolo, che è ciò che il codice hai indicato calcola. Ecco perché memorizziamo una rotazione variabileDegrees: la persona che compone può spostarsi di +90 gradi e quindi di -180, lasciandoci a -90. Una cosa che potrebbe essere sbagliata, però, è che la rotazione positiva è antioraria, nel qual caso "se" (tickNumber> 0) tickNumber = 100 - tickNumber "invece. –

2

Il quadrante deve essere ruotato esattamente di 3,6 gradi per passare da un segno al successivo o precedente. Ogni volta che il tocco dell'utente ruota (attorno al centro) di 3,6 gradi, il quadrante deve essere ruotato di 1 segno (3,6 gradi).

Codice frammento:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() calcola l'angolo di contatto punto WRT dell'utente per comporre centro utilizzando atan2.
  • setDialMark ruota la manopola per il numero di segni modificati.

.

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

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