【问题标题】:Is it possible to disable MotionLayout?是否可以禁用 MotionLayout?
【发布时间】:2019-03-12 10:33:56
【问题描述】:

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

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

在我再次启用之前,是否可以完全禁用 MotionLayout(锁定约束)。禁用拖动识别器也是一个可行的解决方案。

现在一些简化的 XML 来演示这种情况。

包含带有标题视图和回收器视图的运动布局的布局

<?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"

所以回顾一下:我希望能够禁用运动布局,这样我就可以在不扩展标题的情况下滚动 recyclerview,直到我告诉它再次启用。 不幸的是,它不像favouritesMotionLayout.isEnabled = false那么容易,但我就是这么想的。

【问题讨论】:

    标签: android android-motionlayout


    【解决方案1】:

    现在有了 ConstraintLayout beta 2,您可以!这是官方的做法:

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

    【讨论】:

    • 我使用的是 2.0.0-beta7,我不能只使用 sn-p 在运动场景中运行 2 个转换中的 1 个
    • 这不适用于约束布局:2.0.4
    【解决方案2】:

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

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

    当我需要启用我的布局时:

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

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

    【讨论】:

    • 哈哈谢谢伙计。 enableTransition() 有一种官方方法可以做到这一点,但它被窃听了
    【解决方案3】:

    最近我一直在研究 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 是它实际开始移动的地方。 我想反过来你也会明白这个技巧的重点。

    编码愉快!

    【讨论】:

      【解决方案4】:

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

      app:interactionEnabled="false"
      

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

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

      它开箱即用,无需任何额外的绑定适配器。这是在约束布局中添加的:2.1.0-beta01。

      【讨论】:

        【解决方案5】:

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

        Kotlin 中的示例用法:

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

        【讨论】:

        • 它什么也没做,我是唯一一个不能禁用转换的人吗?
        • 相同,无法禁用特定转换并使用该代码运行另一个转换。
        【解决方案6】:

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

        【讨论】:

        • 谢谢,我的案子已经解决了,但我很高兴你也找到了另一个解决方案
        • 谢谢,motionLayout.getTransition(R.id.yourTransition).setEnable(false) 也不适合我
        【解决方案7】:

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

        favouritesMotionLayout.removeView(headerView)

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

        我意识到这不是最漂亮的解决方案,我目前正在寻找在没有运动布局的情况下解决整个用例的方法。这很不幸,因为它对我多达 90% 的用例都非常有用。

        【讨论】:

          【解决方案8】:

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

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

          在 java/kotlin 中

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

          【讨论】:

          • 有趣的想法,但这不会动态工作,对吗?
          【解决方案9】:

          它对我有用:

            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'
          

          【讨论】:

            【解决方案10】:

            如果您想在用户操作完成后禁用转换。只需设置 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)
                    }
            
                })
            

            【讨论】:

              【解决方案11】:

              好吧,我必须这样做,我尝试了 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 来获取 start 和 end 的 start id。但我观察到这还不够。不知何故motionlayout禁用recylerview的触摸处理,但motionlayout的所有其他部分都可以触发转换,所以我需要禁用所有其他视图的触摸处理,让它们只处理点击事件,然后最后不得不做motionlayout 也一样。导致motionlayout处理的视图之间的空间,因此当您触摸motionlayout所在的那些部分时,它将触发过渡。所以也需要禁用它的触摸处理。

              最后它与这个解决方案一起工作!希望这个答案对某人有所帮助! :) 享受吧!

              【讨论】:

                【解决方案12】:

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

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

                【讨论】:

                  【解决方案13】:

                  是的,这是可能的 首先

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

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

                  motionLayout.setTransition(R.id.your_second_Transistion);
                  

                  【讨论】:

                    【解决方案14】:

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

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

                    【讨论】:

                    • 这将禁用所有运动场景。如果您只想禁用一个转换,则此 hack 将不起作用。
                    猜你喜欢
                    • 1970-01-01
                    • 2021-04-17
                    • 2014-10-08
                    • 2014-11-20
                    • 2023-03-18
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-04-13
                    • 2017-06-16
                    相关资源
                    最近更新 更多