6

I have a Gallery full of ImageViews, and the ImageViews are pinch-zoomable and translatable. My goal is that once an ImageView can no longer translate to the left/right, the Gallery will scroll. So sometimes the ImageView needs to handle the touch event, sometimes the Gallery needs to handle the touch event. I have logic in my ImageView's onTouchEvent method for when I want the hand-off to occur, but I'm getting unexpected results. I'll explain the problem after I show my code:

// PinchZoomImageView.java

@Override
public boolean onTouchEvent( MotionEvent event ) {

    Log.i( "PinchZoomImageView", "IM GETTING TOUCHED!" );

    if ( isPassThroughTouchEvent() ) {
        Log.i( "PinchZoomImageView", "IM RETURNING FALSE!" );
        return false;
    }

    getScaleDetector().onTouchEvent( event );

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

            setLastTouchX( x );
            setLastTouchY( y );
            setActivePointerId( event.getPointerId( 0 ) );

            break;
        }

        case MotionEvent.ACTION_MOVE: {
            final int pointerIndex = event.findPointerIndex( getActivePointerId() );
            final float x = event.getX( pointerIndex );
            final float y = event.getY( pointerIndex );

            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if ( !getScaleDetector().isInProgress() ) {
                if ( isDetectMovementX() ) {
                    final float dx = x - getLastTouchX();
                    setPosX( getPosX() + dx );
                }

                if ( isDetectMovementY() ) {
                    final float dy = y - getLastTouchY();
                    setPosY( getPosY() + dy );
                }

                invalidate();
            }

            setLastTouchX( x );
            setLastTouchY( y );

            if ( isAtXBound() && !isPassThroughTouchEvent() ) {

                setPassThroughTouchEvent( true );
            }

            break;
        }

        case MotionEvent.ACTION_UP: {
            setActivePointerId( INVALID_POINTER_ID );
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            setActivePointerId( INVALID_POINTER_ID );
            break;
        }

        case MotionEvent.ACTION_POINTER_UP: {
            final int pointerIndex = ( event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK ) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = event.getPointerId( pointerIndex );
            if ( pointerId == getActivePointerId() ) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                setLastTouchX( event.getX( newPointerIndex ) );
                setLastTouchY( event.getY( newPointerIndex ) );
                setActivePointerId( event.getPointerId( newPointerIndex ) );
            }
            break;
        }
    }

    return true;
}

And here's my Gallery. I overwrote onTouchEvent just to show when it was receiving touch events.

// SwipeGallery.java

@Override
public boolean onTouchEvent( MotionEvent event ) {

    Log.i( "SwipeGallery", "IM GETTING TOUCHED!" );
    return super.onTouchEvent( event );
}

So when I load up the activity, i attempt to swipe from right to left. The logic to pass-through the motion event is immediately triggered, but here's my log output.

08-02 10:04:47.097: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.179: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.179: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.179: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.230: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.230: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.230: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.230: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.245: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.245: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.261: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.261: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.277: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.277: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.296: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.296: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.312: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.312: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.327: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.327: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.343: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.343: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:04:47.360: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:04:47.360: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
....etc.

The SECOND time I swipe right to left, I get this:

08-02 10:27:31.573: INFO/PinchZoomImageView(17189): IM GETTING TOUCHED!
08-02 10:27:31.573: INFO/PinchZoomImageView(17189): IM RETURNING FALSE!
08-02 10:27:31.573: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.636: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.636: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.683: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.933: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.964: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:31.999: INFO/SwipeGallery(17189): IM GETTING TOUCHED!
08-02 10:27:32.034: INFO/SwipeGallery(17189): IM GETTING TOUCHED!

This pattern of "1st motion event the imageview always handles, 2nd motion event the gallery always handles" continues on forever (A new imageview gets made for each position in the gallery which is why isPassThroughTouchEvent() returns false the 3rd, 5th, etc time). So what exactly am I missing here? I thought returning false would propagate the touch event until it was handled, but the Gallery won't take it the first time, but it does the second? This makes no sense to me. Anyone have any ideas? Thanks.

Jason Robinson
  • 31,005
  • 19
  • 77
  • 131
  • Have you tried clearFocus() on gallery? – bgs Aug 04 '11 at 20:02
  • I'd like to keep it a bottom-up approach where the `ImageView` and `Gallery` don't know that either exist and don't need to. Furthermore, any actions taken on the `Gallery` won't affect my first swipe outcome where the `Gallery` never receives any touch events. – Jason Robinson Aug 04 '11 at 20:20
  • I'm curios how to solve this, What does isPassThroughTouchEvent() look like? – bgs Aug 05 '11 at 04:02
  • it's just a boolean setter/getter. I did get what I was wanting to achieve working, but it's far from ideal...I'm overriding `onInterceptTouchEvent()` in my SwipeGallery and passing any motion events to myself then passing the event along so that both my ImageView and SwipeGallery receive and react to the same `MotionEvent`. It's not the solution I'm looking for. – Jason Robinson Aug 05 '11 at 04:47

1 Answers1

3

When a view returns true on the down (ACTION_DOWN) motion event, that view is "locked in" as the touch motion target. Which means that it will receive the subsequent motion events up to the final up event regardless of where it happens on the screen (see this thread), unless if its parent wants and allowed to intercept the event.

To explain your situation:

  1. On the first swipe, your ImageView handled the down motion which makes it the motion target (see the log). That means all subsequent motion events will be delivered to it, and since your Gallery does not intercept the events, its onTouchEvent handler will not be called.

  2. On the second swipe, your ImageView don't handle the down motion (shown in the log with "IM GETTING TOUCH!" + "IM RETURNING FALSE!") and passed the event to the next handler, in this case the Gallery which will run its onTouchEvent handler. By default Gallery always handle the down event, which locks it in as the motion target.

Ricky Lee
  • 1,039
  • 6
  • 6
  • So I've tried resending a `MotionEvent` changing the action to `ACTION_DOWN` via `MotionEvent.obtain(MotionEvent)` and `dispatchTouchEvent(MotionEvent)` with the same result. The event gets sent to my ImageView's `onTouchEvent(MotionEvent)`, I've verified it's a down action and it returns false, with the same results as shown above. – Jason Robinson Aug 05 '11 at 16:18
  • Resending a `MotionEvent`won't change anything. Once a view has become a motion target (by handling the down event) it will be locked in as motion target upto the final up event, unless the parent intercept it. What you need to check is why your `isPassThroughTouchEvent()` returns true on the second swipe. – Ricky Lee Aug 05 '11 at 18:34
  • Because I set it to true. Look inside the case for `ACTION_MOVE` at the bottom. The boolean gets set exactly when I need it to, so it's not the problem. I basically need a way to release the "lock" my image view has on the touch event when that boolean is set to true. – Jason Robinson Aug 05 '11 at 18:37
  • If you want to release the lock, you can just send an `ACTION_CANCEL` to the `ImageView`. – Ricky Lee Aug 05 '11 at 18:44
  • Didn't have an effect. The `ACTION_CANCEL` propagated through the ImageView but the rest of the motion events continued as normal. It seems to treat the dispatched event as it's own set of events. I copied the current motion event via `MotionEvent.obtain(MotionEvent)` then set the action to `ACTION_CANCEL` then called `dispatchTouchEvent(MotionEvent)`. – Jason Robinson Aug 08 '11 at 18:01
  • You'll need to send the `ACTION_CANCEL` via the parent's (`Gallery`) `dispatchTouchEvent()` instead of the `ImageView.dispatchTouchEvent()`. If you only send the `ACTION_CANCEL` to the `ImageView`, the parent will still see the `ImageView` as the motion target. If you send the `ACTION_CANCEL` via the parent, then the parent will clear up the `ImageView` as the motion target. – Ricky Lee Aug 09 '11 at 19:16
  • So is that to say I can't achieve what I'm asking? Basically a way to "ping-pong" touch events back and forth without each View knowing who is actually receiving the event? – Jason Robinson Aug 11 '11 at 16:03