MotionLayout存在子视图截取触摸事件的问题

10

我有一个MotionLayout来控制根容器,在我的主布局中。里面包含其他视图,其中之一是一个FrameLayout,其中包含一个片段。该片段是一个页面,由NestedScrollView等组成......

MotionLayout仅支持水平滑动的OnSwipe,而NestedScrollView只能垂直滚动。我不得不扩展MotionLayout的onInterceptTouchEvent方法(请参阅下面的代码),仅将不是水平的触摸事件传递给子元素。到目前为止非常简单,它有效

问题是,当我开始在吸收某种触摸事件的视图上滑动时(如按钮或NestedScrollView),它会破坏motionLayout的转换,并立即跳过帧,因此锚点与我的鼠标位置相对应。转换几乎完全在框架中进行,并破坏了用户体验。我猜测,如果motionLayout采用X1和X2进行转换,则X1值是导致问题的值,其值来自不应该来自的地方。但当然,当我开始在吸收触摸元素上滑动时。

这是我自定义MotionLayout的OnInterceptTouchEvent的覆盖方法:

 override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        super.onInterceptTouchEvent(event)

        return when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                previousX = event.x.toInt()
                previousY = event.y.toInt()
                mIsScrolling = false
                false
            }
            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                mIsScrolling = false
                false
            }
            MotionEvent.ACTION_MOVE -> {
                if (mIsScrolling) true
                 else {
                    var xMotion: Int = abs(previousX - event.x.toInt())
                    var yMotion: Int = abs(previousY - event.y.toInt())
                    previousX = event.x.toInt()
                    previousY = event.y.toInt()
                    if (xMotion >= yMotion) {
                        mIsScrolling = true
                        true
                    } else false}}
            else -> {
                mIsScrolling = false
                false
            }
        }
    }

这是相关的过渡:

    <Transition android:id="@+id/options_to_now" motion:motionInterpolator="easeInOut"
        motion:pathMotionArc="none"
        motion:constraintSetStart ="@id/options_state"
        motion:constraintSetEnd="@id/now_state"
        motion:duration="100">
        <OnSwipe
            motion:maxAcceleration="100"
            motion:maxVelocity="100"
            motion:dragDirection="dragRight"
            motion:touchAnchorId="@id/view_options"
            motion:touchAnchorSide="left"
            motion:touchRegionId="@id/view_options"/>
    </Transition>

通过测试,我确定MotionDescripton和布局没有问题。

值得注意的是,在我不触摸某些元素(例如布局中的空白空间)时,动画可以完美运行。只有当我在这些元素上滑动时,才会出现上述问题。

也许如果我知道MotionLayout转换与触摸位置的关系,我就可以解决它。

更新1:我发现,如果我点击空白区域(简单的点击ACTION_DOWN),然后滑动,即使从那些调皮的元素开始,问题也不同。那次点击会更新过渡的坐标位置。例如,它将我的点击位置作为起点x1,然后当我在吸收触摸事件的地方滑动时,它会从那里获取x2。现在,在下一帧中看到的结果就像我从x1向x2滑动motion的结果一样。

更新2:另一件我注意到的事情,我认为它与我的问题非常相关,就是在我完成滑动后,转换总是处于STARTED状态。例如,我从x1滑动到x2,它的状态从已启动,已完成,然后重新开始。因此,我想当我的下一个滑动出现时,它会认为我的手指一直在触摸屏幕,并且我手动滑动了,但实际上并没有。在监听转换并打印状态后,日志中的结果如下,当我完成一个完整的滑动手势并完成它时,屏幕上没有手指。因此,最后一个调用的事件是已启动,这对我来说是不合逻辑的。

D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.014359029
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.02863888
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.044761233
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.06311959
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.09285617
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.12685902
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.17269236
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.22314182
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.27269235
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.32545927
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.389359
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.4449254
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.44595188
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.4948284
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.57895637
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.6884479
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.814522
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.8918733
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.95612586
D/e: Changed, state = -1 / p1 = 2131230901 / p2 = 2131230907 / p3 = 0.9949746
D/e: Completed: state = 2131230907 / p1 = 2131230907
D/e: Started, state = 2131230907 / p1 = 2131230901 / p2 = 2131230907
    Changed, state = 2131230907 / p1 = 2131230901 / p2 = 2131230907 / p3 = 1.0

更新3:另一件我注意到的事情是,只有在一个转换成功地改变了动画状态之后,我们才会遇到这样一种情况:点击其中一个触摸吸收元素时,MotionLayout不会接收OnTouchEvent!(这就是存在问题行为的情况),如果当应用程序启动时我没有滑动任何内容(此时还没有问题),通过点击按钮,我在MotionLayout中接收ACTION_DOWN和ACTINO_UP。因此,在状态转换后,无论发生什么事情,都会阻止MotionLayout从其子项接收OnTouchEvents。

更新4:我对MotionLayout如何处理场景创建不是很熟悉,但如果有人知道,它是否可以动态地创建场景,从而阻止onTouchEvent向上传递到父级?

更新5:因此,当motionEvent未被MotionLayout接收时,问题就会出现。我查看了调用堆栈并确定它应该与视图吸收event dispatchTouchEvent方法相关,当在触摸吸收元素上开始滑动时,返回true以ACTION_DOWN为起点,并在其他情况下返回false。因此,在dispatchTouchEvent中返回true会导致MotionLayout未收到ACTION_DOWN并引起问题。在dispatchTouchEvent中,区分两种情况结果的代码部分如下:

  if (onFilterTouchEventForSecurity(event)) {
        ...
        if (!result && onTouchEvent(event)) {
             result = true;
        }
     }

当点击按钮等元素时,OnTouchEvent(event)返回true;当点击空白区域时,返回false。

我正在使用Constraint Layout的2.0.0-beta4版本。


遇到了完全相同的问题。偶然发现,当我开始在消耗触摸的元素(如recyclerview)上滑动时,我的触摸区域会奇怪地偏移到一些无关紧要的位置,比如屏幕的右上角,那里没有吸收触摸的元素。从那里开始滑动就可以正常工作了。 - frangulyan
@frangulyan 你找到解决方案了吗? - mrJoe
为什么要使用motion:touchRegionId="@id/view_options"? 特别是在你重写onInterceptTouchEvent的情况下。 通常使用limitBoundsTo。 - hoford
2个回答

0

我想我找到了问题所在,它就是motionLayout的onInterceptTouchEvent中的mRegionView。在该方法的某个地方,它会将事件x和y的边界与mRegoinView的左、右、上和下进行比较。我查看了这些值,在那些有问题的情况下,这些值有点奇怪,因此函数返回false,从而不调用motionLayout的onTouch事件。以下是这些值:

好的情况 x 354 y 450 top 0 left 16 bottom 1280 rigt 752

坏的情况(如问题描述,当1个转换已成功更改状态为新状态并且我们开始拖动触摸吸收元素时)

x 300 y 450 top 0 left 750 bottom 1280 right 768

正如您所看到的,左侧的值为750,这对我来说有点奇怪,我不知道原因。我可能会检查为什么它具有该值,并更新此答案,但目前我只是在motionLayout的onInterceptTouchEvent中删除了boundCheck,因为我目前至少没有使用特定区域。

更新:问题出在touchRegion和regionId上。对于同一个场景和元素(比如按钮),第一次点击它时,在touchProcess中它可以正常工作,但是如果你进行了转换,那么在touchProcess中将返回一个错误的视图,regionId会返回另一个视图,这是之前转换的场景的父视图。我不知道为什么会发生这种情况,或者这是否是某种错误(考虑到这个库仍处于beta版,可以理解)。因此,我只是覆盖了MotionLayout的touchProcess,并忽略了检查TouchRegion边界的部分。

0

我遇到了一个类似的问题,我的主要RecyclerView中嵌套了其他的RecyclerView

在寻找解决方案时,我发现MotionLayout类中有一个名为setInteractionEnabled(boolean enabled)的方法。将该方法的参数设置为false可以解决我的问题。

看起来MotionLayout默认会在onTouchEventonInterceptTouchEvent等方法中执行一些逻辑。而setInteractionEnabled方法基本上允许我们指定是否需要这些逻辑。


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