Is it possible to disable MotionLayout?

血红的双手。 提交于 2019-12-21 05:05:40

问题


I'v got a Fragment that hosts a RecyclerView within a MotionLayout. On top of the recycler view I've got a view that collapses and expands, all done in my motion scene. It is triggered by a click as well as responding to dragging the recycler view. So far this all works as intended.

Now comes the crux: I'd like to hide the collapsing views completely for some states within my app. But if I set the views visibility or change its height, it still comes back into view when I drag the recyclerview.

So is it possible to disable the MotionLayout completely (Lock the constraints) until I enable it again. To disable the drag recogniser would also be a viable solution.

Now some simplified XML to demonstrate the situation.

The layout containing the motion layout with header view and recyclerview

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.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"
        ...
        />

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

        <pinch.nrcnext.view.scroll.NRCRecyclerView
            android:id="@+id/favoritesList"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </android.support.v4.widget.SwipeRefreshLayout>

</android.support.constraint.motion.MotionLayout>

The corresponding motion scene:

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

Using constraint layout version "2.0.0-alpha3"

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

So recap: I'd like to be able to disable the motion layout, so that I can scroll the recyclerview without the header expanding, until I tell it to be enabled again. Unfortunately it is not as easy as favouritesMotionLayout.isEnabled = false, but that is the way I was thinking.


回答1:


Now with ConstraintLayout beta 2 you can! This is the official way to do it:

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




回答2:


Recently I've been hacking around MotionLayout a lot, so far I found my trick to disable Motionlayout is like below.

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)
                    }
                }
            }
        }
    })

So basically, I'm telling to MotionLayout to go back on certain situations. I feel like onTransitionTrigger or onTransitionStarted should contain the stopping function but it won't listen at those moments.

onTransitionChange is where it actually starts moving. I think you'll get the point of this trick vice versa.

Happy coding!




回答3:


Note that as of the latest beta (beta2), it is possible to disable a transition: https://androidstudio.googleblog.com/2019/06/constraintlayout-200-beta-2.html

Example usage in Kotlin:

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



回答4:


Actually, i managed to get it working by setting the start state of my scene for both start and end. In kotlin:

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

And when i needed to enable my layout:

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

No need to change your scene xml, worked flawlessly for me in alpha-03




回答5:


At this point in time it seems it is not possible to disable the motions in a MotionLayout. I hope this will be added in a future version. To solve my use case I used a bit of a dirty hack: I removed the views making up the header.

favouritesMotionLayout.removeView(headerView)

The layout was okay with this and the view disappeared and the motion was not triggered. To get the views back I just reinflated the entire layout. (An alternative would be to add the views again with their constraints programmatically).

I realize this is not the prettiest solution and I am currently looking into ways to solve the entire use case without motion layout. This is unfortunate as it was very useful for up to 90% of my use case.




回答6:


So it seems there is a way to hack your state to disable the motion transition. It must be added after the transition above in the xml file. I added another transition with the same start and end state. In your case:

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

And in java/kotlin

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



回答7:


It works for me:

  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'



回答8:


If you want to disable the transition once the user action is done. Just set the motionLayout.transitionToState(0). You might want to do this in the transition completed callback.

Check if the below code works for you.

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)
        }

    })



回答9:


Well I had to do this and i tried both beta1 & beta2 library and was not able to disable motionlayout and then finally I did this to disable motionlayout animation when my list is empty:-

SOLUTION

// 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);

In short, we need to setTransition for start id for both start and end. But I had observed that this is not enough. As somehow motionlayout disable the touch handling of recylerview but all other parts of motionlayout can trigger the transition and so I need to disable all other views' touch handling and let them only handle click events and then finally has to do the same for motionlayout too. Cause the space between views handled by motionlayout so when you touch in those parts where motionlayout is, then it will trigger the transition. So need to disable its touch handling too.

And finally it worked with this solution! Hope this answer help someone! :) enjoy!




回答10:


The accepted answer by LPirro was not working for me, I am using beta3 now. Rather I changed the layoutDescription as per my requirement:

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


来源:https://stackoverflow.com/questions/55119362/is-it-possible-to-disable-motionlayout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!