Corresponding rotated object to numeric values

I have a combination lock rotating in a 360 degree circle.

The combination lock has numerical values, they are purely graphic.

I need a way to translate the rotation of the image to values ​​0-99 on the chart.

In this first figure, the value should be able to tell me "0"

In this figure, after the user has rotated the image, the value should be able to tell me "72"

Here is the 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 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 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 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 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); } } } } 

I think I need to translate some information from the matrix into a value of 0-99.

+8
java android rotation translate-animation
source share
4 answers

You must completely reorganize your code. The repeated multiplication of new rotations into the matrix again and again is a numerically unstable calculation. Ultimately, the bitmap will be distorted. Trying to extract the rotation angle from the matrix is ​​too complicated and not needed.

First of all, note that this is a useful previous article on drawing bitmaps with rotation around a selected point.

Just maintain a single double dialAngle = 0 , which is the current dial angle.

You do too much work to get an angle from the point of contact. Let (x0,y0) be the place where sensory interaction begins. While

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

This is the starting angle. When the touch moves to (x,y) , use this coordinate to adjust the dial to the original touch. Then update the matrix and redraw:

 // 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 redrawn in with the new matrix value. 

Note Math.atan2(y, x) does everything you do with quadrants and arcs.

To get a β€œtick” of the current angle, you need 2 pi radians to match 100, so it’s very simple:

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

To find the actual nearest tick as an integer, round the fraction and mod by 100. Note that you can ignore the matrix!

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

This will always work because dialAngle is at [0..2pi). The mod is necessary to display the rounded value 100 back to 0.

+8
source share

To better understand what the matrix does, it’s useful to understand the 2d graphics transformation matrix: http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics . If the only thing you do is rotate (not, say, transform or scale), it is relatively easy to extract the rotation. But, more practical, you can change the rotation code and save the state variable

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

Store the variable to keep the full rotation in degrees, which you increase in your rotation function. Now we know that 3.6 degrees is a tick. Simple math benefits

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

The last thing you need to check: if you have exactly 360 degrees rotation or the number of ticks is 100, you should consider it as 0 (since there is no 100 mark)

+5
source share

It should be a simple multiplication with a scale factor that reduces your degree value (0-359) to a scale of 0-99:

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

EDIT: Fix getAngle Function

For getAngle, you can use atan2 instead, which converts Cartesian coordinates to an angle.

Just save the first coordinate of the touch when touching and moving, you can apply the following calculation:

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

Radians have a range of two pi. modular calculations rotate it so that the value 0 points up. The direction of rotation is counterclockwise.

Therefore, you will need to convert this to degrees and change the direction of rotation to get the right angle.

+4
source share

The dial must rotate exactly 3.6 degrees to move from one mark to the next or previous. Each time the user touches, rotates (around the center) by 3.6 degrees, the dial should rotate by 1 mark (3.6 degrees).

Code snippet:

 float touchAngle = getTouchAngle(); float mark = touchAngle / 3.6f; if (mCurrentMark != mark) { setDialMark(mark); } 
  • getTouchAngle() computes the angle of a user's touch point for dialing using atan2 .
  • setDialMark rotates the disk by the number of labels changed.

.

 void setDialMark(int mark) { rotateDialBy(mCurrentMark - mark); mCurrentMark = mark; } void rotateDialBy(int rotateMarks) { rotationAngle = rotateMarks * 3.6; ... /* Rotate the dial by rotationAngle. */ } 
+2
source share

All Articles