2012-07-12 13 views
8

मेरे पास 360 डिग्री सर्कल में घूर्णन संयोजन लॉक है।संगत घुमावदार वस्तु को संख्यात्मक मानों पर

संयोजन लॉक पर संख्यात्मक मान हैं, ये पूरी तरह से ग्राफिकल हैं।

मुझे ग्राफिक पर 0-99 मानों पर छवि के घूर्णन का अनुवाद करने का एक तरीका चाहिए।

यह पहली ग्राफिक में, मूल्य "0" मुझे बताओ में सक्षम होना चाहिए

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

इस ग्राफिक में, के बाद उपयोगकर्ता छवि घुमाया गया है, मूल्य मुझे बताने की "में सक्षम होना चाहिए

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); 
         } 
       } 
     } 
} 
: 72 "

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

यहाँ कोड है

मुझे लगता है कि मुझे मैट्रिक्स से कुछ जानकारी 0-99 मान में अनुवाद करने की आवश्यकता है।

+2

बस कहना चाहता था, वे सुंदर ग्राफिक्स हैं। –

उत्तर

8

आपको अपने कोड को पूरी तरह से पुनर्गठित करना चाहिए। एक मैट्रिक्स में बार-बार नए घूर्णन को पोस्ट-गुणा करना एक संख्यात्मक रूप से अस्थिर गणना है। आखिरकार बिटमैप विकृत हो जाएगा। मैट्रिक्स से रोटेशन कोण को पुनः प्राप्त करने का प्रयास करना बहुत जटिल और अनावश्यक है।

पहला नोट कि this किसी चुने हुए बिंदु के बारे में रोटेशन के साथ बिटमैप्स ड्राइंग पर एक उपयोगी पूर्व लेख है।

बस एक ही double dialAngle = 0 बनाए रखें जो डायल का वर्तमान रोटेशन कोण है।

आप स्पर्श स्थान से कोण को पुनर्प्राप्त करने के लिए बहुत अधिक काम कर रहे हैं। (x0,y0) उस स्थान पर जाएं जहां स्पर्श शुरू होता है। उस समय,

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

यह प्रारंभिक कोण है। जब स्पर्श (x,y) पर ड्रैग होता है, तो प्रारंभिक स्पर्श के संबंध में डायल समायोजित करने के लिए इस समन्वय का उपयोग करें। फिर मैट्रिक्स को अद्यतन करने और पुनः बनाने:

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

नोट Math.atan2(y, x) क्या आप चतुर्थ भाग और arcsines साथ क्या कर रहे के सभी करता है।

वर्तमान कोण की "टिक" पाने के लिए आपको, 2 pi रेडियंस जरूरत 100 के अनुरूप करने के लिए तो यह बहुत सरल है: अंश दौर,

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

एक पूर्णांक के रूप में वास्तविक निकटतम टिक लगाने के लिए और 100 से मॉड। ध्यान दें कि आप मैट्रिक्स को अनदेखा कर सकते हैं!

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

यह हमेशा काम करेंगे क्योंकि dialAngle [0..2pi में है)। 100 के गोलाकार मान को 0 से 0 0 के लिए मानचित्र की आवश्यकता होती है।

+0

जीन सही है। आपको एक परिवर्तन मैट्रिक्स में जमा नहीं होना चाहिए।उपयोगकर्ता इनपुट लें, इसे "डायलरोटेशन" मान में जमा करें और हर बार ताजा रोटेशन मैट्रिक्स की गणना करें। –

4

यह एक "पैमाने" कारक है कि आपके 0-99 पैमाने के लिए अपने डिग्री मूल्य (0-359) नीचे पैमाने के साथ एक सरल गुणा होना चाहिए:

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

संपादित करें: getAngle समारोह

को ठीक

GetAngle के लिए आप atan2 फ़ंक्शन का उपयोग कर सकते हैं, जो कार्टेसियन निर्देशांक को कोण में बदल देता है।

बस पहला स्पर्श छूने पर समन्वय नीचे की दुकान और इस कदम पर आप निम्नलिखित गणना आवेदन कर सकते हैं:

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

रेडियंस दो पाइ की एक श्रृंखला है। मॉड्यूलो गणना इसे 0 अंक के मान को घुमाती है। घूर्णन दिशा विपरीत दिशा में है।

तो आपको सही कोण प्राप्त करने के लिए डिग्री में बदलने और रोटेशन दिशा बदलने की आवश्यकता होगी।

+0

बहुत अच्छा, मेरे MotionEvent.Action_Move मामले में उत्पन्न चर के साथ कुछ गड़बड़ है, यह खराब हो जाता है और थोड़ा अनुवाद करता है, क्या आप इसे देख सकते हैं? – CQM

+1

@CQM मैंने अपनी कोड बेस से कुछ कॉपी-पेस्ट करके अपनी टिप्पणी अपडेट की जो आपको आवश्यकतानुसार बहुत समान है। यह संभव है कि आपको इसे समायोजित करने की आवश्यकता हो, क्योंकि मैंने पीछे गणित को सत्यापित नहीं किया था। – tiguchi

5

बेहतर समझने के लिए कि मैट्रिक्स क्या करता है, 2 डी ग्राफिक्स ट्रांसफॉर्म मैट्रिक्स को समझना उपयोगी होता है: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics। यदि आप केवल एक चीज कर रहे हैं जो घूर्णन (नहीं, कहें, बदलना या स्केलिंग करना) घूर्णन निकालने के लिए अपेक्षाकृत आसान है। लेकिन, अधिक व्यावहारिक रूप से, आप रोटेशन कोड को संशोधित कर सकते हैं, और एक राज्य चर

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

की दुकान एक चर रखें डिग्री में कुल रोटेशन, जो आप अपने घुमाने समारोह में भी वृद्धि स्टोर करने के लिए। अब, हम जानते हैं कि 3.6 डिग्री एक टिक है। सरल गणित पैदावार

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

एक आखिरी बात आप के लिए जाँच करने के लिए है: आप बिल्कुल 360 डिग्री के एक रोटेशन, या 100 का एक टिक संख्या है, तो आप 0 के रूप में यह इलाज के लिए है (के बाद से वहाँ है कोई टिक 100)

+0

इसके साथ कुछ समस्याएं हैं, मुझे लगता है कि इसे घुमाए जाने वाले वेरिएबल के साथ करना है जो वास्तव में ले रहा है। क्या आप इसे मोशनएवेंट में देख सकते हैं। एक्शन_Move – CQM

+0

घुमावदार वेरिएबल एन्सेप्ट्स कोण में परिवर्तन है, जो कोड है आपने गणना की है। यही कारण है कि हम एक परिवर्तनीय रोटेशन स्टोर करते हैं डिग्री: व्यक्ति डायलिंग +90 डिग्री और फिर -180 स्थानांतरित कर सकता है, हमें -90 पर छोड़ देता है। एक बात जो गलत हो सकती है, हालांकि, यह है कि सकारात्मक रोटेशन विपरीत दिशा में है, जिस स्थिति में 'if (टिक नम्बर> 0) टिकनंबर = 100 - टिकनंबर 'इसके बजाए। –

2

डायल को एक अंक से अगले या पिछले तक जाने के लिए 3.6 डिग्री घुमाया जाना चाहिए। हर बार उपयोगकर्ता का स्पर्श 3.6 डिग्री सेल्सियस (केंद्र के आसपास) घूमता है, डायल को 1 अंक (3.6 डिग्री) से घूर्णन किया जाना चाहिए।

कोड स्निपेट:

float touchAngle = getTouchAngle(); 
float mark = touchAngle/3.6f; 
if (mCurrentMark != mark) { 
    setDialMark(mark); 
} 
  • getTouchAngle() उपयोगकर्ता के स्पर्श बिंदु wrt के कोण की गणना करता है atan2 का उपयोग कर केंद्र डायल करने के लिए।
  • setDialMark अंकों की संख्या से डायल घुमाता है।

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

void rotateDialBy(int rotateMarks) { 
    rotationAngle = rotateMarks * 3.6; 
    ... 
    /* Rotate the dial by rotationAngle. */ 
} 
संबंधित मुद्दे