Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dragging a rotated view by adjusting layout params

I am trying to make a view follow my finger and do some rotation and scaling in multitouch with the following code

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {

    final int action = motionEvent.getActionMasked();
    int newPosX,newPosY;
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final int pointerIndex = motionEvent.getActionIndex();
            final float x = motionEvent.getX( pointerIndex);
            final float y = motionEvent.getY( pointerIndex);

            RelativeLayout.LayoutParams parms = (RelativeLayout.LayoutParams) view.getLayoutParams();

            // Remember where we started (for dragging)
            mLastTouchX = (int) x;
            mLastTouchY = (int) y;
            // Save the ID of this pointer (for dragging)
            mActivePointerId = motionEvent.getPointerId( 0);
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            if(motionEvent.getPointerCount()==2){
                float newDist = spacing(motionEvent);
                float scale = newDist / oldDist * view.getScaleX();
                view.setScaleY(scale);
                view.setScaleX(scale);

                float newAngle = rotation(motionEvent);
                float a = newAngle - oldAngle;
                view.animate().rotationBy(a).setDuration(0).setInterpolator(new LinearInterpolator()).start();
            }
            // Find the index of the active pointer and fetch its position
            final int pointerIndex =
                    motionEvent.findPointerIndex( mActivePointerId);

            final float x = motionEvent.getX( pointerIndex);
            final float y = motionEvent.getY( pointerIndex);

            // Calculate the distance moved
            final float dx = x - mLastTouchX;
            final float dy = y - mLastTouchY;
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();

            layoutParams.leftMargin += dx;
            layoutParams.topMargin += dy;

            view.setLayoutParams(layoutParams);

            break;
        }
        case MotionEvent.ACTION_POINTER_DOWN:{
            oldDist = spacing(motionEvent);
            oldAngle = rotation(motionEvent);
            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 = motionEvent.getActionIndex();
            final int pointerId = motionEvent.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 = (int) motionEvent.getX( newPointerIndex);
                mLastTouchY = (int) motionEvent.getY( newPointerIndex);
                mActivePointerId = motionEvent.getPointerId( newPointerIndex);
            }
            break;
        }
    }
    return true;

}
    private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return (float) Math.sqrt(x * x + y * y);
}

private float rotation(MotionEvent event) {
    double delta_x = (event.getX(0) - event.getX(1));
    double delta_y = (event.getY(0) - event.getY(1));
    double radians = Math.atan2(delta_y, delta_x);
    return (float) Math.toDegrees(radians);
}

Everything is working well until the view is rotated. When it is rotated above 90 degrees and we try to drag it it's jumping around the touch point. I think it has something to do with

layoutParams.leftMargin += dx;
layoutParams.topMargin += dy;

setLayoutParams(layoutParams);

I am working to make this work for last two days with no success.

NOTE: what I am trying to achieve is to make a view drag rotate and scale with 2 fingers(drag with single finger also) I followed the drag code from Google's documentation so as to make it not jump while switching fingers. I am using this to rotate view.animate().rotationBy(a).setDuration(0).setInterpolator(new LinearInterpolator()).start();

because when i use view.setRotate(),the view is vibrating.

EDIT 1: I removed

layoutParams.leftMargin += dx;
layoutParams.topMargin += dy;

and replaced it with:

//To get the moved distance of the finger(X and Y)

float diffX = motionEvent.getX(pointerIndex) - mLastTouchX;
                    float diffY = motionEvent.getY(pointerIndex) - mLastTouchY;

//to get the distance from touch point and the top or left of the view
                    final float dx = motionEvent.getRawX() - (motionEvent.getRawX()-motionEvent.getX());
                    final float dy = motionEvent.getRawY() - (motionEvent.getRawY()-motionEvent.getY());
                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams();

//Settings appropriate value for the margin top and margin left.

                    layoutParams.leftMargin = (int) ((( motionEvent.getRawX() )-dx )+ diffX );
                    layoutParams.topMargin = (int) ((( motionEvent.getRawY() )-dy )+ diffY );

Now even after rotating the view is not moving around. but when i switch the active finger its jumping around from the actual position.

On ACTION_POINTER_UP i am doing this to change shift the active finger

case MotionEvent.ACTION_POINTER_UP: {

                final int pointerIndex = motionEvent.getActionIndex();
                final int pointerId = motionEvent.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;
                    Log.d(TAG,"New pointer index :"+newPointerIndex);
                    mLastTouchX = (int) motionEvent.getX( newPointerIndex);
                    mLastTouchY = (int) motionEvent.getY( newPointerIndex);
                    mActivePointerId = motionEvent.getPointerId( newPointerIndex);
                }
                break;
            }
like image 774
Jim Avatar asked Nov 11 '17 02:11

Jim


1 Answers

Normally I would advise against manipulating the view's position and size excessively on runtime, it updates the layout needlessly lowers performance, hard to control and rarely looks good.

Instead, if you don't want to delve into OpenGl, then my advice would be a Custom View, create a class that extend View which you later add in your layout XML, <com.packagename.MyCustomView ... />. this view will have the size of your whole screen. You @Override the onDraw(Canvas canvas) method, it will give you the canvas of the view, you can use the Canvas to draw graphics in the View.

write invalidate(); in the end of you onDraw() to tell the View to try and call onDraw() again when available, you should stop calling it once user stops touching your view so add some boolean to it.

now the touches, you can register to touch events like you did but inside your custom view and use the x,y and the size of your view from the canvas.getWidth() and canvas.getHeight() to determine what to do.

if you need the size of the view before you reach onDraw() override onMeasure().

Summery

This may not look that easy but it is, and it's fast and quit efficient, the graphics will be smooth, if you want the best possible outcome and are not afraid of lots of code then you can look into drawing using OpenGl but I wouldn't advise you that for what your trying to do.

Hope I understood the problem and helped you - Good Luck!

like image 134
Ariel Yust Avatar answered Oct 21 '22 18:10

Ariel Yust