是否可以禁用 MotionLayout?

Is it possible to disable MotionLayout?

我有一个 FragmentMotionLayout 中托管 RecyclerView。在回收站视图之上,我有一个可以折叠和展开的视图,所有这些都是在我的运动场景中完成的。它由单击触发以及响应拖动回收器视图。到目前为止,这一切都按预期工作。

现在是症结所在:我想在我的应用程序中完全隐藏某些状态的折叠视图。 但是,如果我设置视图可见性或更改其高度,当我拖动 recyclerview 时它仍然会返回视图。

那么是否可以完全禁用 MotionLayout(锁定约束)直到我再次启用它。禁用拖动识别器也是一个可行的解决方案。

现在进行一些简化XML来演示情况。

包含带有 header 视图和 recyclerview 的运动布局的布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/favouritesMotionLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/favourites_motion_scene"
    android:animateLayoutChanges="true">

    <ImageView
        android:id="@+id/headerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ...
        />

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        ...
    >

        <androidx.recyclerview.widget.RecyclerVieww
            android:id="@+id/favoritesList"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.motion.widget.MotionLayout>

对应的运动场景:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto"
>

    <Transition
        motion:constraintSetStart="@id/expanded"
        motion:constraintSetEnd="@id/collapsed"
        motion:duration="300">
        <OnSwipe
            motion:touchAnchorId="@id/swipeRefreshLayout"
            motion:touchAnchorSide="top"
            motion:dragDirection="dragUp" />
    </Transition>

    <ConstraintSet android:id="@+id/expanded">
        ... Constraints for expanded header ...
    </ConstraintSet>

    <ConstraintSet android:id="@+id/collapsed">
        ... ... Constraints for collapsed header ...
    </ConstraintSet>

</MotionScene>

使用约束布局版本“2.0.0-alpha3”

implementation "com.android.support.constraint:constraint-layout:2.0.0-alpha3"

所以回顾一下:我希望能够禁用运动布局,这样我就可以在不展开 header 的情况下滚动回收视图,直到我告诉它再次启用为止。 不幸的是,它不像 favouritesMotionLayout.isEnabled = false 那样简单,但这就是我的想法。

目前似乎无法在 MotionLayout 中禁用动作。我希望这将在未来的版本中添加。 为了解决我的用例,我使用了一些肮脏的技巧:我删除了构成 header.

的视图

favouritesMotionLayout.removeView(headerView)

布局没问题,视图消失了,也没有触发动作。为了恢复视图,我只是重新膨胀了整个布局。 (另一种方法是以编程方式再次添加视图及其约束)。

我意识到这不是最漂亮的解决方案,我目前正在研究无需运动布局即可解决整个用例的方法。这很不幸,因为它对我 90% 的用例都非常有用。

最近我一直在研究 MotionLayout,到目前为止我发现我禁用 Motionlayout 的技巧如下所示。

layoutContainer.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 onTransitionCompleted(p0: MotionLayout?, p1: Int) {}
        override fun onTransitionChange(p0: MotionLayout?, startedScene: Int, endScene: Int, p3: Float) {
            when(currentFragmentTag) {
                TAG_DEFAULTBACKGROUND ->  {
                    if( startedScene==R.id.halfExpanded ) {
                        layoutContainer.transitionToState(startedScene)
                    }
                }
            }
        }
    })

所以基本上,我告诉 MotionLayout 在某些情况下返回。 我觉得 onTransitionTriggeronTransitionStarted 应该包含停止功能,但它不会在那些时刻收听。

onTransitionChange 是它真正开始移动的地方。 我想你会明白这个技巧的意义,反之亦然。

编码愉快!

所以似乎有一种方法可以破解您的状态以禁用运动转换。它必须在 xml 文件中的上述转换之后添加。 我添加了另一个具有相同开始和结束状态的转换。 你的情况:

<Transition
        motion:constraintSetStart="@id/collapsed"
        motion:constraintSetEnd="@id/collapsed"
        motion:duration="0">

并且在java/kotlin

motion_layout.setTransition(R.id.collapsed, R.id.collapsed)

实际上,我设法通过为开始和结束设置场景的开始状态来让它工作。在科特林中:

motionLayout.setTransition(R.id.start, R.id.start)

当我需要启用布局时:

motionLayout.setTransition(R.id.start, R.id.end)

无需更改场景 xml,在 alpha-03 中对我来说完美无缺

对我有用:

  if (list.isNullOrEmpty()) {
                    //disable
                    motionLayout.apply {
                        setTransition(R.id.expanded, R.id.expanded)
                        setTransitionDuration(0)
                    }
                } else {
                   //enable
                    motionLayout.apply {
                        setTransition(R.id.expanded, R.id.collapsed)
                        setTransitionDuration(200)
                    }
                }





 p.s.: 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'

请注意,从最新的测试版 (beta2) 开始,可以禁用转换: https://androidstudio.googleblog.com/2019/06/constraintlayout-200-beta-2.html

Kotlin 中的示例用法:

(view as? MotionLayout)?.getTransition(R.id.swipe_transition)?.setEnable(false)

如果您想在用户操作完成后禁用转换。只需设置 motionLayout.transitionToState(0)。您可能希望在转换完成回调中执行此操作。

检查以下代码是否适合您。

motionLayout.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) {
            (motionLayout as MotionLayout).transitionToState(0)
        }

    })

现在您可以使用 ConstraintLayout beta 2!这是官方的做法:

motionLayout.getTransition(R.id.yourTransition).setEnable(false)

好吧,我不得不这样做,我尝试了 beta1 和 beta2 库,但无法禁用 motionlayout,最后我这样做是为了在我的列表为空时禁用 motionlayout 动画:-

解决方案

// this fix the transition but other parts of layout will still able to trigger transition somehow to R.id.end!!
motionLayout.setTransition(R.id.start, R.id.start);
// disable all other views touch handling except view clicks.
motionLayout.findViewById(R.id.view_ids).setOnTouchListener(
          (v, event) -> event.getActionMasked() != MotionEvent.ACTION_UP);
motionLayout.findViewById(R.id.view_ids).setOnTouchListener(
          (v, event) -> event.getActionMasked() != MotionEvent.ACTION_UP);
     .... disable all the views touch handling cause they can trigger the motion layout animation!

// And then finally has to disable touch handling of motionlayout itself to avoid the space exposed by motionlayout due to padding or margin which can trigger the transition!
motionLayout.setOnTouchListener(
          (v, event) -> event.getActionMasked() != MotionEvent.ACTION_UP);

简而言之,我们需要setTransition作为开始和结束的起始id。但我观察到这还不够。由于某种原因 motionlayout 禁用了 recylerview 的触摸处理,但是 motionlayout 的所有其他部分都可以触发转换,所以我需要禁用所有其他视图的触摸处理,让它们只处理点击事件然后最后也必须对 motionlayout 做同样的事情。导致由 motionlayout 处理的视图之间的 space 因此当您触摸 motionlayout 所在的那些部分时,它将触发转换。所以也需要禁用它的触摸处理。

终于可以使用这个解决方案了!希望这个答案能帮助别人! :) 享受吧!

接受的答案对我不起作用,我现在正在使用 beta3。 相反,我根据我的要求更改了 layoutDescription:

public void enableTransition(boolean enable) {
    if (enable) loadLayoutDescription(R.xml.scene);
    else loadLayoutDescription(0);
}

我正在使用 androidx.constraintlayout:constraintlayout:2.0.0-beta4 motionLayout.getTransition(R.id.yourTransition).setEnable(false) 对我不起作用,但要完全禁用运动布局,例如在我的情况下,我需要在特殊情况下隐藏视图,但此视图涉及动画。所以 cardMotionLayout.loadLayoutDescription(0) 完全禁用 MotionLayout,并再次启用我使用 cardMotionLayout.loadLayoutDescription(R.xml.fragment_buy_card_step_one_scene)

我找到了一个方便的解决方案来暂时禁用运动布局转换。 在 xml 中将其设置为您的 MotionLayout :

app:interactionEnabled="false"

该解决方案的优点是直接在 xml 文件中使用它,这使您可以选择连接到数据绑定

app:interactionEnabled="@{viewModel.shouldInteractionBeActive}"

它开箱即用,无需任何额外的绑定适配器。这是在 constraintlayout:2.1.0-beta01.

中添加的

是的,这是可能的 第一

motionLayout.getTransition(R.id.yourTransition).setEnable(false)

现在用 Transistion 替换它,什么都不做

motionLayout.setTransition(R.id.your_second_Transistion);

禁用 MotionLayout 动画转换的最简单方法是在 MotionLayout 上设置属性 app:applyMotionScene="false"

属性 applyMotionScene 是格式布尔值,用于启用/禁用 MotionLayout 中的过渡。