1. Welcome to the newer, better version of Android Forums! Be sure to see the announcement and enjoy checking it out!

    Some of you have been having login issues. - Please try now. Sorry for the trouble!
  2. All attachments uploaded on the first day of this new look need to be re-uploaded, or will appear broken. All prior to that, and all going forward, should work fine. We apologize for the inconvenience!

Bug in Rotating ImageView Android with two fingers


  1. AssetFX

    AssetFX New Member

    Hi,
    in the following code I have setup a few gestures for an ImageView.
    All was well until I got to implementing rotation functionality with two fingers. Before I added this a two fingers gesture was used to pan and scale the image using the focus point as its center.


    Now adding another level of complexity to it, with the rotation, it has come undone.
    - The first bug is that the pan is losing movement if at a scale larger than 1 and gaining more movement if less than one.
    - The second bug is that if you scale the image then lift your fingers, place them somewhere else the image jumps.


    Obviously it is to do with the scale but I am having issues trying to figure the resolution.
    The piece where I recalculate mPosX and mPosY in RotatePoint2D is the one causing the headache but it may be the figures I throwing in as well.


    Any help would greatly be appreciated

    Code:
    import android.content.Context;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.MotionEvent;
    import android.view.ScaleGestureDetector;
    import android.view.animation.DecelerateInterpolator;
    import android.view.animation.Interpolator;
    import android.widget.ImageView;
    
    public class MyImageView extends ImageView {
        
        private static final int INVALID_POINTER_ID = -1;
    
        private float mPosX;
        private float mPosY;
    
        private float mLastTouchX;
        private float mLastTouchY;
        private float mLastGestureX;
        private float mLastGestureY;
        private float mRotationDegrees = 0;
        private int mActivePointerId = INVALID_POINTER_ID;
        private int mActivePointerId2 = INVALID_POINTER_ID;
        float oL1X1, oL1Y1, oL1X2, oL1Y2;
    
        private ScaleGestureDetector mScaleDetector;
        private GestureDetector mGestureDetector;
        
        private float mScaleFactor = 1.f;
        
        //The following variable control the fling gesture
        private Interpolator animateInterpolator;
        private long startTime;
        private long endTime;
        private float totalAnimDx;
        private float totalAnimDy;
        private float lastAnimDx;
        private float lastAnimDy;
    
        public MyImageView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
            mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
            mGestureDetector = new GestureDetector(getContext(), new GestureListener());
        }
    
        public MyImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
            mGestureDetector = new GestureDetector(context, new GestureListener());
        }
        
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            // Let the ScaleGestureDetector inspect all events.
            mScaleDetector.onTouchEvent(ev);
            mGestureDetector.onTouchEvent(ev);
    
            final int action = ev.getAction();
            switch (action & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN: {
                    if (!mScaleDetector.isInProgress()) {
                        final float x = ev.getX();
                        final float y = ev.getY();
                        
                        mLastTouchX = x;
                        mLastTouchY = y;
                        mActivePointerId = ev.getPointerId(0);
                    }
                    break;
                }
                case MotionEvent.ACTION_POINTER_DOWN: {
                    if (mScaleDetector.isInProgress()) {
                        mActivePointerId2 = ev.getPointerId(1);
                        
                        mLastGestureX = mScaleDetector.getFocusX();
                        mLastGestureY = mScaleDetector.getFocusY();
                        
                        oL1X1 = ev.getX(ev.findPointerIndex(mActivePointerId));
                        oL1Y1 = ev.getY(ev.findPointerIndex(mActivePointerId));
                        oL1X2 = ev.getX(ev.findPointerIndex(mActivePointerId2));
                        oL1Y2 = ev.getY(ev.findPointerIndex(mActivePointerId2));
                    }
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    
                    // Only move if the ScaleGestureDetector isn't processing a gesture.
                    if (!mScaleDetector.isInProgress()) {
                        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                        final float x = ev.getX(pointerIndex);
                        final float y = ev.getY(pointerIndex);
                        
                        final float dx = x - mLastTouchX;
                        final float dy = y - mLastTouchY;
                        
                        mPosX += dx;
                        mPosY += dy;
                        
                        invalidate();
                        
                        mLastTouchX = x;
                        mLastTouchY = y;
                    }
                    else{
                        final float gx = mScaleDetector.getFocusX();
                        final float gy = mScaleDetector.getFocusY();
                        
                        final float gdx = gx - mLastGestureX;
                        final float gdy = gy - mLastGestureY;
                        
                        mPosX += gdx;
                        mPosY += gdy;
                        
                        float nL2X1, nL2Y1, nL2X2, nL2Y2;
                        nL2X1 = ev.getX(ev.findPointerIndex(mActivePointerId));
                        nL2Y1 = ev.getY(ev.findPointerIndex(mActivePointerId));
                        nL2X2 = ev.getX(ev.findPointerIndex(mActivePointerId2));
                        nL2Y2 = ev.getY(ev.findPointerIndex(mActivePointerId2));
                        
                        float angleDifference = angleBetween2Lines(oL1X1, oL1Y1, oL1X2, oL1Y2, nL2X1, nL2Y1, nL2X2, nL2Y2);
                        RotatePoint2D(gx, gy, mPosX, mPosY, angleDifference);
                        mRotationDegrees += angleDifference;
                        
                        oL1X1 = nL2X1;
                        oL1Y1 = nL2Y1;
                        oL1X2 = nL2X2;
                        oL1Y2 = nL2Y2;
                        
                        invalidate();
    
                        mLastGestureX = gx;
                        mLastGestureY = gy;
                    }
                    
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    mActivePointerId = INVALID_POINTER_ID;
                    
                    break;
                }
                case MotionEvent.ACTION_CANCEL: {
                    mActivePointerId = INVALID_POINTER_ID;
                    break;
                }
                case MotionEvent.ACTION_POINTER_UP: {
                    
                    final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
                            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    final int pointerId = ev.getPointerId(pointerIndex);
                    if (pointerId == mActivePointerId) {
                        // This was our active pointer going up. Choose a new
                        // active pointer and adjust accordingly.
                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                        mLastTouchX = ev.getX(newPointerIndex);
                        mLastTouchY = ev.getY(newPointerIndex);
                        mActivePointerId = ev.getPointerId(newPointerIndex);
                    }
                    else{
                        final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);
                        mLastTouchX = ev.getX(tempPointerIndex);
                        mLastTouchY = ev.getY(tempPointerIndex);
                    }
                    
                    break;
                }
            }
    
            return true;
        }
    
        @Override
        public void onDraw(Canvas canvas) {
            canvas.save();
            canvas.translate(mPosX, mPosY);
            if (mScaleDetector.isInProgress()) {
                canvas.rotate(mRotationDegrees);
                canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
            }
            else{
                canvas.rotate(mRotationDegrees);
                canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
            }
            super.onDraw(canvas);
            canvas.restore();
        }
    
        private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                mScaleFactor *= detector.getScaleFactor();
    
                // Don't let the object get too small or too large.
                mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
    
                invalidate();
                return true;
            }
        }
    
        private class GestureListener extends GestureDetector.SimpleOnGestureListener {
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                
                if (e1 == null || e2 == null){
                    return false;
                }
                final float distanceTimeFactor = 0.4f;
                final float totalDx = (distanceTimeFactor * velocityX/2);
                final float totalDy = (distanceTimeFactor * velocityY/2);
                
                onAnimateMove(totalDx, totalDy, (long) (1000 * distanceTimeFactor));
                return true;
            }
        }
        
        public void onAnimateMove(float dx, float dy, long duration) {
            animateInterpolator = new DecelerateInterpolator();
            startTime = System.currentTimeMillis();
            endTime = startTime + duration;
            totalAnimDx = dx;
            totalAnimDy = dy;
            lastAnimDx = 0;
            lastAnimDy = 0;
            
            post(new Runnable() {
                @Override
                public void run() {
                    onAnimateStep();
                }
            });
        }
        
        private void onAnimateStep() {
            long curTime = System.currentTimeMillis();
            float percentTime = (float) (curTime - startTime) / (float) (endTime - startTime);
            float percentDistance = animateInterpolator.getInterpolation(percentTime);
            float curDx = percentDistance * totalAnimDx;
            float curDy = percentDistance * totalAnimDy;
            
            float diffCurDx = curDx - lastAnimDx;
            float diffCurDy = curDy - lastAnimDy;
            lastAnimDx = curDx;
            lastAnimDy = curDy;
            
            doAnimation(diffCurDx, diffCurDy);
            
            if (percentTime < 1.0f) {
                post(new Runnable() {
                    @Override
                    public void run() {
                        onAnimateStep();
                    }
                });
            }
        }
        
        public void doAnimation(float diffDx, float diffDy) {
            mPosX += diffDx;
            mPosY += diffDy;
            
            invalidate();
        }
        
        public float angleBetween2Lines(float L1X1, float L1Y1, float L1X2, float L1Y2, float L2X1, float L2Y1, float L2X2, float L2Y2)
        {
            float angle1 = (float) Math.atan2(L1Y1 - L1Y2, L1X1 - L1X2);
            float angle2 = (float) Math.atan2(L2Y1 - L2Y2, L2X1 - L2X2);
            
            float angleDelta = findAngleDelta( (float)Math.toDegrees(angle1), (float)Math.toDegrees(angle2));
            return -angleDelta;
        }
        
        private float findAngleDelta( float angle1, float angle2 )
        {
            return angle1 - angle2;
        }
        
        private void RotatePoint2D(float ox, float oy, float px, float py, float angle)   
        {
            double thetaAngle = Math.toRadians(angle);
            
            //If you rotate point (px, py) around point (ox, oy) by angle theta you'll get:
            mPosX = (float)(Math.cos(thetaAngle) * (px-ox) - Math.sin(thetaAngle) * (py-oy) + ox);
            mPosY = (float)(Math.sin(thetaAngle) * (px-ox) + Math.cos(thetaAngle) * (py-oy) + oy);
            //Log.d("","ox: " + ox + " oy: " + oy + " px: " + px + " py: " + py + " angle: " + angle + " thetaAngle: " + thetaAngle + " mPosX: " + mPosX + " mPosY: " + mPosY);
        }
    }

    Advertisement

Share This Page