视图之间触摸事件传递的问题

6
我有一个包含多个 ImageViewGallery,这些 ImageView 可以进行缩放和平移。我的目标是当一个 ImageView 无法继续向左/右平移时,Gallery 将开始滚动。因此,有时需要 ImageView 处理触摸事件,有时需要 Gallery 处理触摸事件。我在 ImageViewonTouchEvent 方法中编写了逻辑,用于确定何时需要进行交接,但结果并不如预期。在展示代码之后,我将解释问题所在:
// 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;
}

这是我的画廊。我重写了onTouchEvent只是为了展示它何时接收到触摸事件。

// SwipeGallery.java

@Override
public boolean onTouchEvent( MotionEvent event ) {

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

当我加载这个活动时,我尝试从右向左滑动。传递运动事件的逻辑立即被触发,但以下是我的日志输出。
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.

第二次从右向左滑动时,我看到了这个:
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!

这种“第一次运动事件总是由ImageView处理,第二次运动事件总是由Gallery处理”的模式将一直持续(为每个画廊中的位置制作一个新的ImageView,这就是为什么isPassThroughTouchEvent()在第3、5次等返回false)。那么我到底缺了什么?我认为返回false会传播触摸事件,直到处理完毕,但第一次Gallery不会接受它,但第二次会接受?这对我来说没有意义。有没有人有任何想法?谢谢。

你尝试过在画廊上使用clearFocus()吗? - bgs
我想采用自下而上的方法,其中ImageViewGallery不知道彼此的存在,也不需要知道。此外,在Gallery上执行的任何操作都不会影响我的第一次滑动结果,因为Gallery永远不会接收到任何触摸事件。 - Jason Robinson
我很好奇如何解决这个问题,isPassThroughTouchEvent()看起来像什么? - bgs
这只是一个布尔值的设置器/获取器。虽然我已经实现了我想要的功能,但离理想状态还有很大差距...我在我的SwipeGallery中重写了onInterceptTouchEvent()方法,并将任何动作事件传递给自己,然后再将事件传递给ImageView和SwipeGallery,以便两者都能接收并响应相同的MotionEvent。这不是我正在寻找的解决方案。 - Jason Robinson
1个回答

3

当一个视图在按下(ACTION_DOWN)事件中返回真时,该视图被“锁定”为触摸运动目标。这意味着它将接收随后的动作事件,直到最终抬起事件,无论它发生在屏幕的什么位置(参见此线程),除非它的父级想要并且 允许截取事件。

解释你的情况:

  1. 在第一次滑动中,您的ImageView处理了按下运动,这使它成为运动目标(请参阅日志)。这意味着所有随后的动作事件都将传递给它,而由于您的Gallery没有截取这些事件,因此它的onTouchEvent处理程序将不会被调用。

  2. 在第二次滑动中,您的ImageView 不处理按下事件(在日志中显示为“IM GETTING TOUCH!”+“IM RETURNING FALSE!”,并将事件传递给下一个处理程序,即此情况下的Gallery,它将运行其onTouchEvent处理程序。默认情况下,Gallery始终处理按下事件,这将锁定它作为运动目标。


所以我尝试通过使用MotionEvent.obtain(MotionEvent)dispatchTouchEvent(MotionEvent)将操作更改为ACTION_DOWN来重新发送MotionEvent,但结果相同。该事件被发送到我的ImageView的onTouchEvent(MotionEvent),我已经验证它是一个按下动作并返回false,结果与上面显示的相同。 - Jason Robinson
重新发送MotionEvent不会改变任何内容。一旦一个视图成为移动目标(通过处理按下事件),它将被锁定为移动目标,直到最后的抬起事件,除非父级拦截它。你需要检查的是为什么在第二次滑动时isPassThroughTouchEvent()返回true。 - Ricky Lee
如果你想释放锁定,你可以向 ImageView 发送一个 ACTION_CANCEL - Ricky Lee
没有产生影响。ACTION_CANCEL 通过 ImageView 传播,但其余的运动事件继续正常进行。它似乎将分派的事件视为自己的一组事件。我通过 MotionEvent.obtain(MotionEvent) 复制了当前的运动事件,然后将操作设置为 ACTION_CANCEL,最后调用了 dispatchTouchEvent(MotionEvent) - Jason Robinson
那么这是否意味着我不能实现我所要求的?基本上是一种“乒乓式”触摸事件来回传递,而不需要每个视图都知道谁实际上接收到了事件? - Jason Robinson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接