对应的旋转对象为数字值

我有一个360度旋转的组合锁。

密码锁上有数字值,这些都是纯粹的graphics。

我需要一种方法将图像的旋转转换为graphics上的0-99值。

在这第一个graphics中,值应该能够告诉我“0”

http://sofzh.miximages.com/java/27y67b7.png

在这个graphics中,用户旋转图像后,该值应该能够告诉我“72”

http://sofzh.miximages.com/java/2ueiogh.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); } } } } 

我想我需要将matrix中的一些信息转换为0-99的值。

Solutions Collecting From Web of "对应的旋转对象为数字值"

你应该完全重新组织你的代码。 一次又一次地将新的旋转倍增到matrix中是一个数值不稳定的计算。 最终位图将变形。 试图从matrix中检索旋转angular度太复杂和不必要。

首先要注意的是, 这是一个有用的关于绘制位图旋转关于所选点的有用的文章。

只需保持一个double dialAngle = 0即表盘的当前旋转angular度。

您正在做太多的工作来从触摸位置检索angular度。 设(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); 

这是开始的angular度。 当触摸拖动到(x,y) ,使用此坐标调整相对于初始触摸的拨号。 然后更新matrix并重绘:

 // 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)用象限和反正弦来做所有的事情。

为了得到当前angular度的“tick”,你需要2 pi的弧度来对应100,所以非常简单:

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

要查找实际最接近的整数,请将整数分数和模数除以100.注意,您可以忽略matrix!

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

这将始终工作,因为dialAngle在[0..2pi)。 MOD需要将100的舍入值映射回0。

为了更好地理解matrix的作用,理解二维graphics变换matrix是有帮助的: http : //en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics 。 如果你正在做的唯一的事情是旋转(不是说,转换或缩放),提取旋转是相对容易的。 但是,更实际的是,您可以修改轮换代码,并存储状态variables

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

保留一个variables来存储以度为单位的总旋转,在旋转函数中增加。 现在,我们知道3.6度是一个滴答声。 简单的math产量

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

最后一件事,你必须检查:如果你有一个完全 360度的旋转,或者一个100的刻度,你必须把它当作0(因为没有勾号100)

这应该是一个简单的乘法,将您的学位值(0-359)缩小到0-99的“比例”因子:

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

编辑:更正getAngle函数

对于getAngle,您可以使用atan2函数,而将笛卡尔坐标转换为angular度。

只需将触摸下的第一个触摸坐标存储起来,即可应用以下计算:

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

弧度有两个pi的范围。 模数计算旋转它,所以0的值指向上。 旋转方向是逆时针方向。

所以你需要将其转换为度数并改变旋转方向以获得正确的angular度。

表盘应正好旋转3.6度,从一个标记转到下一个或前一个标记。 每当用户的触摸(中心附近)旋转3.6度时,表盘应该旋转1个标记(3.6度)。

代码片段:

 float touchAngle = getTouchAngle(); float mark = touchAngle / 3.6f; if (mCurrentMark != mark) { setDialMark(mark); } 
  • getTouchAngle()使用atan2来计算用户的触摸点的angular度来拨打中心。
  • setDialMark通过改变的标记数来旋转拨号盘。

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