Is it possible use motion layout with different transitions?

Google makes this comment on their Google Developers medium blog:

Note: It is possible to define multiple ConstraintSets within a MotionScene, so if you have a multi-step motion where such steps are valid “resting” state, you can use them instead of keyframes. Transitioning state to state would have to be done in code (change listeners are available).

So I was able to do it by creating a listener:

motion_layout.setTransitionListener(
    object : MotionLayout.TransitionListener {
        override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
        }

        override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
            if (currentId == R.id.two) {
                //if the 1st transition has been made go to the second one
                intro_motion_layout.setTransition(R.id.two, R.id.three)
                intro_motion_layout.transitionToEnd()
            }
        }
    }
)
intro_motion_layout.transitionToEnd()

And then in my MotionScene I had three transitions:

<?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/one"
    motion:constraintSetEnd="@+id/two"
    motion:duration="200"
    motion:interpolator="linear" />

<ConstraintSet android:id="@+id/one">
    <Constraint />
</ConstraintSet>

<ConstraintSet android:id="@+id/two">
    <Constraint />
</ConstraintSet>

<ConstraintSet android:id="@+id/three">
    <Constraint />
</ConstraintSet>


I faced a similar issue wherein I had defined 2 transitions: A -> B (will refer as transition X) B -> C (will refer as transition Y) both triggered on clicking the animated view.

It was the order of transitions in the motion scene file that mattered in my case. Whichever transition was defined first would get executed first. So, if transition Y was defined above transition X, then only B -> C -> B transitions were possible. However, on placing transition X above transition Y in the motion scene file, following transitions became possible - A -> B -> C -> B.

Note: A -> B could still only be performed once.


After hours and hours of trying different things, I finally found out why it wasn't working for me. I knew it must be possible because google provides a sample that has multiple transitions https://github.com/googlesamples/android-ConstraintLayoutExamples/blob/multi_state/motionlayout/src/main/res/xml/scene_26.xml. But no matter what I tried, only the first transition in the xml file would work. What I finally realized, is that in order for a transition to work it, the current state of the UI must match the begin or end state of the transition as defined in the constraint set. Not only that, but you can't have the same constraint set defined more than once, meaning if you have two different transitions that can happen on the initial screen, both transitions need to share the constraint set.

I created a simple example of two buttons that move with different translations. I couldn't find a simple example of this anywhere so hopefully this will help some people. As you'll see in the video, when the left to right text has moved to the right, you can't move the top to bottom text to the bottom because that state was not defined in a transition.

Finally, the last gotcha I encountered is every constraint set seems to have to contain every view that is part of a transition, regardless of whether it is in the current transition. Otherwise views just seem to move at random.

enter image description here

Here is the layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:showIn="@layout/activity_main"
        app:layoutDescription="@xml/motion_scene"
        tools:context=".MainActivity">

    <TextView
            android:id="@+id/left_to_right_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Left to right"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <TextView
            android:id="@+id/top_to_bottom_text"
            android:text="Top to bottom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

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

And this is the MotionScene

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


    <Transition
            app:constraintSetStart="@id/base"
            app:constraintSetEnd="@id/bottom">
        <OnClick
                app:targetId="@id/top_to_bottom_text">

        </OnClick>

    </Transition>

    <Transition
            app:constraintSetStart="@id/base"
            app:constraintSetEnd="@id/right">
        <OnClick
                app:targetId="@id/left_to_right_text">

        </OnClick>

    </Transition>


    <ConstraintSet android:id="@+id/base">

        <Constraint
                android:id="@id/left_to_right_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

        <Constraint
                android:id="@id/top_to_bottom_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/bottom">

        <Constraint
                android:id="@id/left_to_right_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

        <Constraint
                android:id="@id/top_to_bottom_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
        />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/right">

        <Constraint
                android:id="@id/left_to_right_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

        <Constraint
                android:id="@id/top_to_bottom_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>
    </ConstraintSet>
</MotionScene>

Tags:

Android