6

I've created an onTouchListener for dragging Views. Images drag smoothly if I use getRawX() and getRawY(). The problem with that is the image will jump to the second pointer when you place a second pointer down then lift the first pointer.

This onTouchListener attempts to fix that issue by keeping track of the pointerId. The problem with this onTouchListener is while dragging an ImageView, the ImageView jumps around pretty crazily. The getX() and getY() values jump around.

I feel like I'm doing this correctly. I don't want to have to write a custom view for this because I've already implemented a scaleGestureDetector and written a custom rotateGestureDetector that work. Everything works fine but I need to fix the issue I get when using getRawX() and getRawY().

Does anybody know what I'm doing wrong here?

Here's my onTouchListener:

final View.OnTouchListener onTouchListener = new View.OnTouchListener()
{
    @Override
    public boolean onTouch(View v, MotionEvent event)
    {
        relativeLayoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();

        final int action = event.getAction();
        switch (action & MotionEvent.ACTION_MASK)
        {
            case MotionEvent.ACTION_DOWN:
            {
                final float x = event.getX();
                final float y = event.getY();

                // Where the user started the drag
                lastX = x;
                lastY = y;
                activePointerId = event.getPointerId(0);
                break;
            }
            case MotionEvent.ACTION_MOVE:
            {
                // Where the user's finger is during the drag
                final int pointerIndex = event.findPointerIndex(activePointerId);
                final float x = event.getX(pointerIndex);
                final float y = event.getY(pointerIndex);

                // Calculate change in x and change in y
                final float dx = x - lastX;
                final float dy = y - lastY;

                // Update the margins to move the view
                relativeLayoutParams.leftMargin += dx;
                relativeLayoutParams.topMargin += dy;
                v.setLayoutParams(relativeLayoutParams);

                // Save where the user's finger was for the next ACTION_MOVE
                lastX = x;
                lastY = y;

                v.invalidate();
                break;
            }
            case MotionEvent.ACTION_UP:
            {
                activePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            {
                activePointerId = INVALID_POINTER_ID;
                break;
            }
            case MotionEvent.ACTION_POINTER_UP:
            {
                // Extract the index of the pointer that left the touch sensor
                final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = event.getPointerId(pointerIndex);

                if(pointerId == activePointerId)
                {
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    lastX = (int) event.getX(newPointerIndex);
                    lastY = (int) event.getY(newPointerIndex);
                    activePointerId = event.getPointerId(newPointerIndex);
                }
                break;
            }
        }
        return true;
    }
};
image1.setOnTouchListener(onTouchListener);
Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • Anybody have a solution to this? Seems like others have run into this issue, but I haven't found a solution yet. – Ben Kane Jul 08 '13 at 16:27

3 Answers3

12

The issue was simple, but unexpected. getRawX/Y() returns absolute coordinates, while getX/Y() returns coordinates relative to the view. I would move the view, reset lastX/Y, and the image wouldn't be in the same spot anymore so when I get new values they'd be off. In this case I only needed where I originally pressed the image (not the case when using `getRawX/Y').

So, the solution was to simply remove the following:

// Save where the user's finger was for the next ACTION_MOVE
lastX = x;
lastY = y;

I hope this will help somebody in the future, because I've seen others with this problem, and they had similar code to me (resetting lastX/Y)

Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • Glad I found this! I suspect many people have code for saving the lastX/LastY because of reading http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html . Apparently the situation of moving a view differs from the example in "multitouch" where a canvas is being translated. The last comment here, http://stackoverflow.com/questions/16676097/android-getx-gety-interleaves-relative-absolute-coordinates?rq=1, notes that when he commented out the setting of view.setX() and view.setY(), the interleaving of absolute and relative coordinates during onTouch() stopped. – T. Folsom Oct 30 '13 at 19:06
  • @T.Folsom Glad I could help! Yes, a lot of people model their code after your first link, myself included. As far as I know, `getX()` and `getY()` always return relative coordinates. His coordinates were jumping for the same reason as me. He moves the view then gets different relative coordinates because the view has shifted. Does that make sense? – Ben Kane Nov 01 '13 at 18:45
  • Yes, this did help more people :) – nsg Mar 06 '14 at 17:22
  • 1
    2019, still helpful. I've got the sample code from the official docs (https://developer.android.com/training/gestures/scale) and inherited this issue. Thanks! – Vasiliy Apr 26 '19 at 16:40
  • @Vasiliy Wonderful :) I’m glad the answer has held up – Ben Kane Apr 26 '19 at 16:45
1

After a lot of research, I found this to be the issue, getRawX is absolute and getX is relative to view. hence use this to transform one to another

//RawX = getX + View.getX

event.getRawX == event.getX(event.findPointerIndex(ptrID1))+view.getX()
Maurice
  • 570
  • 6
  • 8
0

I have One tip & think it will help.

invalidate() : does only redrawing a view,doesn't change view size/position.

What you should use is requestLayout(): does the measuring and layout process. & i think requestLayout() will be called when call setLayoutParams();

So Try by removing v.invalidate()

or try using view.layout(left,top,right,bottom) method instead of setting layoutParams.

Sreejith B Naick
  • 1,203
  • 7
  • 13
  • I've tried removing `v.invalidate()` in the past (tried it again just now since you suggested it) since I figured it's useless in this scenario, doesn't help though. I tried replacing it with `v.requestLayout()` and that didn't change the behavior either :/ Any other suggestions? – Ben Kane Jul 08 '13 at 18:26
  • or try using view.layout(left,top,right,bottom) method instead of setting layoutParams. – Sreejith B Naick Jul 09 '13 at 04:44