Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awkward touch event propagation between views

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.

like image 508
Jason Robinson Avatar asked Aug 02 '11 15:08

Jason Robinson


1 Answers

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.

like image 137
Ricky Lee Avatar answered Sep 27 '22 23:09

Ricky Lee