儿童拦截触摸事件时的MotionLayout问题



我在主布局中有一个根容器的motionLayout。里面还有其他的景色。其中之一是frameLayout,其中包含一个片段。片段是一个页面,由NestedScrollView等组成。。。MotionLayout具有仅用于水平滑动的OnSwipe,并且NestedScrollView应该只能垂直滚动。我不得不扩展MotionLayout onInterceptTouchEvent方法(查看下面的代码),并且只将那些非水平的触摸事件传递给孩子。到目前为止相当简单,而且有效

问题是,当我开始在吸收某种触摸事件(如按钮或NestedScrollView)的视图上滑动时,它会打乱motionLayout的转换,并瞬间跳过帧,因此锚点与我的鼠标位置相对应。过渡几乎完全在一帧中进行,并扼杀了UI体验。我的猜测是,如果motionLayout采用X1和X2作为过渡,X1的值就是问题的根源,它的值来自于它不应该出现的地方。但当然,当我开始在触摸吸收元素上滑动时。

这是我的customMotionLayout:的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滑动运动一样。

更新2:我注意到的另一件事,我认为这与我的问题非常相关,那就是当我完成滑动时,转换总是处于STARTED状态。就像我从x1滑动到x2一样,它的状态从开始、完成到开始。所以我想,当我的下一次滑动出现时,它认为我的手指一直在触摸,我手动地siwp,但我没有。当我在屏幕上没有手指的情况下完成一个完整的刷geture和完成它的循环时,在听了转换和弹完状态后,日志中的结果如下。所以最后一个被调用的事件被启动了,这对我来说是不合乎逻辑的

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在转换到新状态后发生什么,它都会阻止motionLayout从子级接收OnTouchEvents。

更新4:我不太熟悉MotionLayout是如何处理场景创建的,但如果有人知道的话,可能是MotionLayout创建了一个场景动态,以某种方式阻止onTouchEvent一直传播到父母?

更新5:因此,当motionLayout没有接收到ACTION_DOWN的motionEvent时,问题就出现了。我查看了调用堆栈,认为它应该与视图吸收事件dispatchTouchEvent方法有关,当开始在触摸吸收元素上滑动时,ACTION_DOWN返回true,而在其他情况下返回false。因此,在dispatchTouchEvent中返回true会导致motionLayout无法接收ACTION_DOWN并导致问题。在dispatchTouchEvent中,区分两种情况的结果的代码是:

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

OnTouchEvent(事件)在例如点击按钮时返回true,在点击空格时返回false。

我使用的是约束布局2.0.0-beta4版本。

我想我发现了问题,是motionLayout的onInterceptTouchEvent中的mRegionView。在该方法中,它检查事件x和y与mRegoinView的左右、顶部和底部的边界。我查看了这些值,发现在这些有问题的情况下,这些值有点奇怪,因此函数返回false,并且从不调用motionLayout的onTouch事件。值为:

良好的情况x 354y 450前0左16底部1280rigt 752

糟糕的情况(如问题中所述,当1已经成功地将状态转换为新状态,并且我们开始拖动触摸吸收元件时)

x 300y 450前0左750底部1280右768

正如你所看到的,左边有750,这对我来说有点可怕,我不知道原因。我可能会检查它为什么有这个值,并更新这个答案,但目前我只是在motionLayout中的onInterceptTouchEvent中删除了boundCheck,因为至少目前我还没有真正使用spicific区域。

更新:是touchRegion和regionId。对于相同的场景和元素(比如按钮),当你第一次点击它时,[在touchProcess]中]它工作得很好,但如果你进行了转换,那么它在touchProcess中返回了一个错误的视图,regionId返回了另一个视图,它是上一个转换场景的父视图。我不知道为什么会发生这种情况,或者是某种错误(考虑到这个库仍然是测试版,这是可以理解的)。所以我只是高估了MotionLayout的touchProcess,忽略了检查TouchRegion边界的部分。

我偶然发现了一个类似的问题,我有一个嵌套了RecyclerView的主RecyclerView

在搜索解决方案时,我在MotionLayout类中发现了一个名为setInteractionEnabled(boolean enabled)的方法。使用false值调用该方法解决了我的问题。

默认情况下,MotionLayoutonTouchEventonInterceptTouchEvent等方法中执行一些逻辑。方法setInteractionEnabled基本上允许我们指定是否需要该逻辑。

最新更新