2012-07-12 19 views
8

Mam blokadę kombinacji obracającą się w okręgu 360 stopni.Odpowiadający obrócony obiekt na wartości numeryczne

zamkiem szyfrowym ma wartości liczbowe na nim, są czysto graficzny.

Potrzebuję sposobu na przetłumaczenie obrotu obrazu na wartości 0-99 na grafice.

W tej pierwszej grafice, wartość powinna być w stanie powiedzieć mi „0”

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

W tej grafice, gdy użytkownik obraca obraz, wartość powinna być w stanie powiedzieć mi " 72"

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

Oto kod:

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

Myślę, że muszę przetłumaczyć niektóre informacje z macierzy na wartości 0-99.

+2

Chciałem tylko powiedzieć, że to piękna grafika. –

Odpowiedz

8

Powinieneś całkowicie zreorganizować swój kod. Powtórne pomnożenie nowych rotacji w macierz jest wielokrotnie niestabilnym obliczeniem. W końcu bitmapa zostanie zniekształcona. Próba odzyskania kąta obrotu z matrycy jest zbyt złożona i niepotrzebna.

Pierwsza uwaga, że ​​this jest przydatnym wcześniejszym artykułem na temat rysowania bitmap z obrotem wokół wybranego punktu.

Wystarczy zachować pojedynczy double dialAngle = 0, który jest bieżącym kątem obrotu tarczy.

Robisz zbyt dużo pracy, aby uzyskać kąt z miejsca dotyku. Niech (x0,y0) będzie miejscem, w którym zaczyna się dotyk. W tym czasie:

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

To jest kąt początkowy. Kiedy dotknięcie przeciągnie do (x,y), użyj tej współrzędnej, aby wyregulować pokrętło względem początkowego dotknięcia. Następnie zaktualizować matrycę i przerysować:

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

Uwaga Math.atan2(y, x) robi wszystko to, co robisz z ćwiartek i arcsines.

Aby uzyskać „cyk” aktualnego kąta, trzeba 2 radianów pi odpowiadać do 100, więc jest to bardzo prosta:

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

znaleźć rzeczywiste najbliższym zaznaczyć liczbę całkowitą, okrągłe frakcji i mod przez 100. Uwaga: możesz zignorować matrycę!

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

To zawsze zadziała, ponieważ dialAngle jest w [0..2pi). Mod jest potrzebny do odwzorowania zaokrąglonej wartości 100 z powrotem na 0.

+0

Gene ma rację. Nie powinno się gromadzić w macierzy transformacji.Weź dane wejściowe od użytkownika, skumuluj je w wartości "dialRotation" i za każdym razem oblicz nowe matryce obrotu. –

4

To powinno być proste mnożenie z "wagi" czynnik, który skaluje dół swoją wartość stopnia (0-359) do skali 0-99:

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

EDIT: Poprawianie funkcję getAngle

Dla getAngle można użyć funkcji atan2, która przekształca współrzędne kartezjańskie na kąt.

Wystarczy przechowywać pierwszy dotykowy koordynować na dotyk i ruch w dół można zastosować następujące obliczenia:

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

W radianów mają szereg dwóch pi. obliczenia modulo obracają ją o wartość 0 punktów w górę. Kierunek obrotu jest przeciwny do ruchu wskazówek zegara.

Więc trzeba by przekonwertować do stopni i zmienić kierunek obrotów dla uzyskania prawidłowego kąta.

+0

Bardzo fajnie, coś jest nie tak ze zmiennymi generowanymi w mojej sprawie MotionEvent.Action_Move, to się trochę wkurza i przekłada, możesz na to patrzeć? – CQM

+1

@CQM Zaktualizowałem swój komentarz, kopiując wklejając coś z mojej bazy kodu, która jest bardzo podobna do tego, czego potrzebujesz. Możliwe, że musisz to zmienić, ponieważ nie zweryfikowałem matematyki. – tiguchi

5

Aby lepiej zrozumieć działanie macierzy, pomocne jest zrozumienie macierzy przekształcenia grafiki 2D: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics. Jeśli jedyną rzeczą, którą robisz, jest obracanie (nie, powiedzmy, przekształcanie lub skalowanie), stosunkowo łatwo jest wyodrębnić obrót. Ale bardziej praktycznie, można zmodyfikować kod rotacji i przechowywać zmienną stanu

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

przechowywać zmiennej do przechowywania całkowity obrót w stopniach, którego przyrost swojej funkcji obrócić. Teraz wiemy, że 3.6 stopnia to kleszcz. Proste plony matematyczne

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

Jeden ostatnią rzeczą, którą trzeba sprawdzić: Jeśli masz obrót dokładnie 360 stopni lub numer kleszcz 100, trzeba traktować go jako 0 (ponieważ nie ma bez zaznaczenia 100)

+0

jest kilka problemów z tym, myślę, że ma to związek ze zmiennymi, które rotateDialer faktycznie bierze. Czy możesz to sprawdzić w MotionEvent.Action_Move – CQM

+0

Zmienna, którą akceptuje rotateDialer to zmiana kąta, co jest tym, co kod wskazałeś obliczenia. Dlatego przechowujemy zmienną rotationDegrees: osoba wybierająca może przesunąć o +90 stopni, a następnie -180, pozostawiając nas na poziomie -90. Jedną z rzeczy, które mogą być błędne, jest to, że dodatnia rotacja jest przeciwna do ruchu wskazówek zegara, w takim przypadku "jeśli (tickNumber> 0) tickNumber = 100 - tickNumber" zamiast. –

2

Pokrętło powinno być obrócone dokładnie o 3,6 stopnia, aby przejść od jednego znaku do następnego lub poprzedniego. Za każdym razem, gdy dotyk użytkownika obraca się (wokół środka) o 3.6 stopnia, pokrętło powinno być obrócone o 1 znak (3.6 stopnia).

Fragment kodu:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() oblicza kąt dotykowym punktu wrt użytkownika, aby wybrać centrum użyciu atan2.
  • setDialMark obraca pokrętło o liczbę oznaczeń zmienionych.

.

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

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