2012-07-12 13 views
8

Ich habe ein Kombinationsschloss, das sich in einem 360 Grad Kreis dreht.Entsprechendes gedrehtes Objekt zu numerischen Werten

Das Zahlenschloss hat Zahlenwerte, diese sind rein grafisch.

Ich brauche eine Möglichkeit, um die Bildrotation auf die 0-99-Werte in der Grafik zu übersetzen.

In dieser ersten Grafik sollte der Wert der Lage sein, mir zu sagen, „0“

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

In dieser Grafik, nachdem der Benutzer das Bild gedreht hat, soll der Wert der Lage sein, mir zu sagen " 72"

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

Hier ist der Code:

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

Ich denke, ich muss einige Informationen aus der Matrix in einen 0-99 Wert übersetzen.

+2

Ich wollte nur sagen, das sind schöne Grafiken. –

Antwort

8

Sie sollten Ihren Code vollständig reorganisieren. Die wiederholte Multiplikation neuer Rotationen in eine Matrix ist eine numerisch instabile Berechnung. Schließlich wird die Bitmap verzerrt. Der Versuch, den Rotationswinkel aus der Matrix zu erhalten, ist zu komplex und unnötig.

Beachten Sie zuerst, dass this ein nützlicher vorheriger Artikel zum Zeichnen von Bitmaps mit Drehung um einen ausgewählten Punkt ist.

Nur eine einzige double dialAngle = 0 beibehalten, die der aktuelle Drehwinkel des Zifferblattes ist.

Sie tun viel zu viel Arbeit, um den Winkel von der Touch-Position abzurufen. Lassen Sie (x0,y0) der Ort sein, an dem die Berührung beginnt. Zu dieser Zeit

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

Dies ist der Startwinkel. Wenn die Berührung auf (x,y) gezogen wird, verwenden Sie diese Koordinate, um das Zifferblatt in Bezug auf die anfängliche Berührung anzupassen. Dann aktualisieren Sie die Matrix und neu zu zeichnen:

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

Hinweis Math.atan2(y, x) tut alles, was man mit Quadranten und arcsines tun.

Um den „Tick“ des aktuellen Winkels zu erhalten, müssen Sie 2 pi Radian bis 100 entsprechen, ist es so einfach:

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

Um die tatsächliche nächste Zecke als eine ganze Zahl, um die Fraktion zu finden und mod von 100. Beachten Sie, dass Sie die Matrix ignorieren können!

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

Das wird immer funktionieren, da dialAngle in [0..2pi ist). Der Mod wird benötigt, um einen gerundeten Wert von 100 wieder auf 0 zu setzen.

+0

Das Gen hat Recht. Sie sollten sich nicht in einer Transformationsmatrix ansammeln.Nehmen Sie die Benutzereingabe, akkumulieren Sie sie in einen "dialRotation" -Wert und berechnen Sie jedes Mal frische Rotationsmatrizen daraus. –

4

Dies sollte eine einfache Multiplikation mit einem „Maßstab“ Faktor sein, der Ihren Grad Wert herunterskaliert (0-359) auf Ihre 0-99 Skala:

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

EDIT: die Funktion getAngle Korrektur

Für getAngle können Sie stattdessen die atan2 Funktion verwenden, die kartesische Koordinaten in einen Winkel umwandelt.

speichern Sie einfach die erste Aufsetzpunkt auf Touch koordinieren und auf Bewegung können Sie die folgende Berechnung gilt:

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

Die Radiant haben eine Reihe von zwei pi. die Modulo-Berechnungen drehen es also um einen Wert von 0 Punkten nach oben. Die Drehrichtung ist gegen den Uhrzeigersinn.

Sie müssten also in Grad konvertieren und die Drehrichtung ändern, um den richtigen Winkel zu erhalten.

+0

sehr cool, es ist etwas falsch mit den Variablen in meinem MotionEvent.Action_Move Fall generiert, dies vermasselt und Übersetzung ein wenig, können Sie das betrachten? – CQM

+1

@CQM Ich habe meinen Kommentar aktualisiert, indem ich etwas aus meiner Codebasis kopiert habe, das dem, was Sie brauchen, ziemlich ähnlich ist. Es ist möglich, dass Sie es anpassen müssen, weil ich die Mathematik dahinter nicht überprüft habe. – tiguchi

5

Um besser zu verstehen, was die Matrix tut, ist es hilfreich, 2D-Grafik-Transformationsmatrizen zu verstehen: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics. Wenn Sie nur rotieren (nicht etwa transformieren oder skalieren), ist es relativ einfach, die Rotation zu extrahieren. Aber noch praktisch, können Sie den Rotationscode ändern und speichern Sie eine Zustandsgröße

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

eine Variable Halten Sie die Gesamtdrehung in Grad zu speichern, die Sie in Ihrer Drehfunktion erhöhen. Jetzt wissen wir, dass 3,6 Grad eine Zecke ist. Einfache mathematische Erträge

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

Die eine letzte, was Sie überprüfen: Wenn Sie eine Rotation von genau 360 Grad, oder eine Zecke Zahl von 100 haben, müssen Sie es als 0 behandeln (da es kein Haken 100)

+0

Es gibt einige Probleme mit diesem, ich denke, es hat mit den Variablen zu tun, die rotateDialer tatsächlich einnimmt. Können Sie dies in MotionEvent.Action_Move betrachten – CQM

+0

Die Variable, die rotateDialer akzeptiert, ist eine Änderung im Winkel, was ist der Code Sie haben berechnet. Deshalb speichern wir eine Variable rotationDegrees: die Person, die wählt, kann +90 Grad und dann -180 bewegen und uns bei -90 lassen. Eine Sache, die falsch sein kann, ist jedoch, dass die positive Drehung gegen den Uhrzeigersinn ist, in diesem Fall "if (tickNumber> 0) tickNumber = 100 - tickNumber". –

2

Das Zifferblatt sollte genau 3,6 Grad gedreht werden, um von einer Markierung zur nächsten oder vorherigen zu gelangen. Jedes Mal, wenn sich die Berührung des Benutzers (um die Mitte) um 3,6 Grad dreht, sollte das Einstellrad um eine Markierung (3,6 Grad) gedreht werden.

Code-Snippet:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() den Winkel des Benutzers WRT Berührungspunkt berechnet Zentrum mit atan2 zu wählen.
  • setDialMark dreht das Zifferblatt um die Anzahl der geänderten Marken.

.

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

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