Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Motion layout not animating views that are not its direct children

I have recently tried the MotionLayout, I works fine on a button when it is a direct child of the MotionLayout but the same motion scene does not work, when I enclose the button in another layout, bu the parent layout is still MotionLayout.

First layout where the button is direct child :-

<?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:layout_width="match_parent"
  app:layoutDescription="@xml/demo"
  android:layout_height="match_parent"
  tools:context=".Demo" >

  <Button
    android:layout_width="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:layout_height="wrap_content"
    android:id="@+id/yellow_button"
     />


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

Second layout where button is indirect child :-

<?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:layout_width="match_parent"
app:layoutDescription="@xml/demo"
android:layout_height="match_parent"
tools:context=".Demo"
>
<LinearLayout
   android:layout_width="match_parent"
   android:id="@+id/l1"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toTopOf="parent"
   android:layout_height="wrap_content">
  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/yellow_button"/>
 </LinearLayout>

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

The motion scene layout is below:-

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

<ConstraintSet android:id="@+id/start">
    <Constraint android:id="@+id/yellow_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >

        <CustomAttribute app:attributeName="alpha"
            app:customFloatValue="0.0"/>

    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint android:id="@id/yellow_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:alpha="1.0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        >

        <CustomAttribute app:attributeName="alpha"
            app:customFloatValue="1.0"/>

    </Constraint>

</ConstraintSet>

<Transition
    app:constraintSetEnd="@id/end"

    app:autoTransition="animateToEnd"
    app:constraintSetStart="@+id/start"
    app:duration="2000"/>

Is there any guideline that needs to be followed in these cases??

OR

Does this mean that only direct children of the MotionLayout can be animated with it?

like image 968
Kaveri Avatar asked Oct 27 '25 21:10

Kaveri


2 Answers

This medium article (https://medium.com/google-developers/introduction-to-motionlayout-part-i-29208674b10d) by Google Developers says under the 'Limitations' section: "MotionLayout will only provide its capabilities for its direct children — contrary to TransitionManager, which can work with nested layout hierarchies as well as Activity transitions."

like image 179
Myk Avatar answered Oct 30 '25 12:10

Myk


I found out a way to make it works, it involves a few lines of code and I have not tried it with so complex MotionLayouts but at least for a typically beautiful nested XML it works like a charm.
Check out this animation example:

This is how XML hierarchy looks like

<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/root_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_main_scene"
tools:context=".MainActivity">

<androidx.constraintlayout.motion.widget.MotionLayout
    android:id="@+id/header_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layoutDescription="@xml/header_container_scene"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/header_background"
        android:foreground="@drawable/white_gradient"
        android:scaleType="centerCrop"
        android:src="@drawable/tonitan_unsplash"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/logo"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_marginTop="12dp"
        android:contentDescription="@string/logo"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_launcher_foreground"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.motion.widget.MotionLayout
        android:id="@+id/child_header_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        app:layoutDescription="@xml/child_header_container_scene"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/logo">

        <TextView
            android:id="@+id/header_text_1"
            style="@style/Title.White"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_header"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/header_text_2"
            style="@style/Title.White"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/header_value"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toEndOf="@id/header_text_1"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/another_text_1"
            style="@style/Title.White"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:text="@string/my_subtitle"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/header_text_1" />

        <TextView
            android:id="@+id/another_text_2"
            style="@style/Title.White"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:text="@string/subtitle_value"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toEndOf="@id/another_text_1"
            app:layout_constraintTop_toBottomOf="@id/header_text_1" />

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

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

<TextView
    android:id="@+id/my_app_title"
    style="@style/Title.Black.25"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="12dp"
    android:layout_marginEnd="16dp"
    android:text="@string/my_title"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/header_container" />

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_marginTop="12dp"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/my_app_title"
    tools:listitem="@layout/rv_item" />

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

The magic

As you can see in above XML, there are three scenes: activity_main_scene, header_container_scene, child_header_container_scene. Each one will take care about its direct children and the only missing thing is synch them up:

  1. Implement MotionLayout.TransitionListener in your Activity, Fragment or independent class.
  2. Create the scenes you need (in the example I have 3)
  3. set up the transaction listener for each one (call this fun in onCreate, onViewCreated or something like that):
private fun setUpMotionLayoutListener() = with(binding) {
        rootContainer.setTransitionListener(this@MainActivity)
        headerContainer.setTransitionListener(this@MainActivity)
        childHeaderContainer.setTransitionListener(this@MainActivity)
    }
  1. synchronise them using following code:
private fun updateNestedMotionLayout(motionLayout: MotionLayout?) = motionLayout?.let {
        with(binding) {
            if (it.id == rootContainer.id) {
                headerContainer.progress = it.progress
                childHeaderContainer.progress = it.progress
            }
        }
    }

And that's all!

The complete example can be found here

like image 36
MiguelHincapieC Avatar answered Oct 30 '25 11:10

MiguelHincapieC



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!