MotionLayout:如何在特定视图上限制 'OnSwipe' 事件而不是在整个屏幕上限制 'OnSwipe'

MotionLayout: How to Limit the 'OnSwipe' event on a specific view instead of 'OnSwipe' on whole screen

我有一个带有以下内容的动画布局'layoutDescription'(场景文件)

    <Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">

    <OnSwipe
        motion:touchAnchorId="@+id/button"
        motion:touchAnchorSide="left"
        motion:dragDirection="dragLeft" />

</Transition>
<ConstraintSet android:id="@+id/start">
    <!--    ConstraintSet for starting -->
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <!--    ConstraintSet for end -->
</ConstraintSet>

我在这里的期望是,如果用户将 'button' 向左侧拖动,那么它应该执行过渡,但实际发生的是当用户从屏幕中的任何位置向左拖动(向左滑动)时,这个过渡被处决!

如何限制动画仅在拖动指定视图时起作用?

(我正在使用 "constraintlayout:2.0.0-beta2")

更新的答案:使用 ConstraintLayout 2.0.0 alpha-5,您可以简单地使用

app:touchRegionId

在您的 "OnSwipe" 中有同样的行为。也可以将 'app:targetId' 用于 "OnClick" 用法。

感谢@RuslanLeshchenko


原回答:

我最终通过扩展 MotionLayout 和覆盖 onTouchEvent 将事件转发到所需视图来设法做到这一点。

通过扩展 MotionLayout 创建自定义布局

class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) {

private var viewToDetectTouch1 :View? = null
private var viewToDetectTouchRes :Int = -1

private val viewRect = Rect()
private var touchStarted = false

init {

    setTransitionListener(object : MotionLayout.TransitionListener {
        override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {

        }

        override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {

        }

        override fun onTransitionChange(p0: MotionLayout, p1: Int, p2: Int, p3: Float) {
        }

        override fun onTransitionCompleted(p0: MotionLayout, p1: Int) {
            touchStarted = false
        }
    })

    context.theme.obtainStyledAttributes(
            attributeSet,
            R.styleable.SingleViewTouchableMotionLayout,
            0, 0).apply {

        try {
            viewToDetectTouchRes = getResourceId(
                    R.styleable.SingleViewTouchableMotionLayout_viewToDetectTouch, 0)
        } finally {
            recycle()
        }
    }

}

override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    viewToDetectTouch1 =  (parent as View).findViewById<View>(viewToDetectTouchRes)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.actionMasked) {
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            touchStarted = false
            return super.onTouchEvent(event)
        }
    }
    if (!touchStarted) {
        viewToDetectTouch1?.getHitRect(viewRect)
        touchStarted = viewRect.contains(event.x.toInt(), event.y.toInt())
    }
    return touchStarted && super.onTouchEvent(event)
}

}

并添加自定义属性,该属性将用于声明将接收事件以触发 Transition/animation 的视图。 将这些样式添加到 res 文件夹中的 attrs.xml (res/values/attrs.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SingleViewTouchableMotionLayout">
        <attr name="viewToDetectTouch" format="reference" />
    </declare-styleable>
</resources>

就是这样!! 这样我们就可以在指定视图上归档 MotionLayout OnSwipe。

布局 XML 文件现在将如下所示:

 <your_package.SingleViewTouchableMotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion_file"

    app:viewToDetectTouch="@+id/triggerView"

        >
    <!-- Notice that 'app:viewToDetectTouch' hold the id of the  view we interested -->


    <!-- View we interested  -->
    <View

        android:id="@+id/triggerView"

        android:layout_width="50dp"
        android:layout_height="50dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:background="@color/colorBlack"
        />

    </your_package.SingleViewTouchableMotionLayout>

实际上对于 onSwipe 你可以使用 touchRegionIdtargetId 对于 OnClick