【问题标题】:MotionLayout problems with children intercepting touch events儿童拦截触摸事件的 MotionLayout 问题
【发布时间】:2020-04-17 15:22:40
【问题描述】:

我的主布局中有根容器的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 过渡与 touchPositions 的关系,我可以解决它。

更新 1:我注意到如果我单击空白区域(只需单击 ACTION_DOWN),然后滑动,即使从那些顽皮的元素开始,问题也会有所不同。该点击以某种方式更新了过渡的协调位置。它以我单击的位置为起点,例如 x1。然后,当我在吸收触摸事件的东西上滑动时,它会从那里得到它的 x2。现在我在下一帧看到的结果就像我从 x1 滑动到 x2 一样。

更新 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 中发生什么,它都会阻止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(event) 在单击按钮时返回 true,在单击空格时返回 false。

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

【问题讨论】:

  • 遇到完全相同的问题。偶然发现,当我开始在消耗触摸的元素上滑动时,比如 recyclerview,我的触摸区域奇怪地转移到了一些不相关的位置,比如屏幕右上角没有触摸吸收元素。从那里刷卡工作。
  • @frangulyan 你找到解决方案了吗?
  • 你为什么使用 motion:touchRegionId="@id/view_options" ?特别是因为您正在覆盖 onInterceptTouchEvent。一般使用limitBoundsTo

标签: android android-constraintlayout android-touch-event android-motionlayout


【解决方案1】:

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

情况不错 × 354 是 450 前 0 左 16 底部1280 装备752

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

x 300 是 450 前 0 还剩 750 底部1280 右768

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

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

【讨论】:

    【解决方案2】:

    我偶然发现了一个类似的问题,我有一个主要的RecyclerView,其中嵌套了RecyclerViews。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多