MotionLayout + RecyclerView = buenas listas animadas

En este artículo, le explicaré y le mostraré cómo crear hermosas listas animadas basadas en RecyclerView y MotionLayout. Usé un método similar en uno de mis proyectos.





Del traductor: El repositorio del autor del artículo es https://github.com/mjmanaog/foodbuddy .

Lo bifurqué para traducirlo. Quizás la "versión rusa" se adapte más a alguien.





¿Qué es MotionLayout?

En resumen, MotionLayout es una subclase de ConstraintLayout que le permite describir el movimiento y la animación de los elementos ubicados en ella utilizando XML. Más detalles: en la documentación y aquí con ejemplos.





Vamos a empezar.





Paso 1: crea un nuevo proyecto

Llamémoslo como quieras. Seleccione Actividad vacía como actividad.





Paso 2: agregue las dependencias requeridas

Agregue al archivo gradle de la aplicación:





implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
      
      



Y comencemos la sincronización (Sync Now en la esquina superior derecha).





Paso 3: crea un diseño

Nuestro elemento de la lista futura se verá así:





Elemento de lista RecyclerView
RecyclerView

res/layout item_food.





<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/clMain"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layoutDescription="@xml/item_food_scene">

    <ImageView
        android:id="@+id/ivFood"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_marginTop="8dp"
        android:elevation="10dp"
        android:scaleType="fitXY"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/img_salmon_salad" />

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_marginStart="100dp"
        android:layout_marginLeft="100dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        app:cardCornerRadius="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:textSize="18sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="  " />

            <TextView
                android:id="@+id/tvDescription"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginEnd="16dp"
                android:layout_marginRight="8dp"
                android:ellipsize="end"
                android:maxLines="3"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tvTitle"
                tools:text="   —     .   ,        : ,   ." />

            <TextView
                android:id="@+id/tvCalories"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginBottom="16dp"
                android:textStyle="bold"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imageView6"
                tools:text="80 " />

            <ImageView
                android:id="@+id/imageView6"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:srcCompat="@drawable/ic_calories" />

            <ImageView
                android:id="@+id/imageView7"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_marginStart="24dp"
                android:layout_marginLeft="24dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/tvCalories"
                app:srcCompat="@drawable/ic_star" />

            <TextView
                android:id="@+id/tvRate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imageView7"
                tools:text="4.5" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>
      
      



4: ConstraintLayout MotionLayout

ConstraintLayout MotionLayout:





  • Split Design;





  • (Component Tree) ( — clMain);





  • Convert to MotionLayout.





Cómo convertir ConstraintLayout a MotionLayout
ConstraintLayout MotionLayout

MotionLayout.





item_food
<?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/clMain"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layoutDescription="@xml/item_food_scene">

    <ImageView
        android:id="@+id/ivFood"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_marginTop="8dp"
        android:elevation="10dp"
        android:scaleType="fitXY"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/img_salmon_salad" />

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_marginStart="100dp"
        android:layout_marginLeft="100dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        app:cardCornerRadius="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:textSize="18sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="  " />

            <TextView
                android:id="@+id/tvDescription"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginEnd="16dp"
                android:layout_marginRight="8dp"
                android:ellipsize="end"
                android:maxLines="3"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tvTitle"
                tools:text="   —     .   ,        : ,   ." />

            <TextView
                android:id="@+id/tvCalories"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginBottom="16dp"
                android:textStyle="bold"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imageView6"
                tools:text="80 " />

            <ImageView
                android:id="@+id/imageView6"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_marginStart="60dp"
                android:layout_marginLeft="60dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:srcCompat="@drawable/ic_calories" />

            <ImageView
                android:id="@+id/imageView7"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_marginStart="24dp"
                android:layout_marginLeft="24dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/tvCalories"
                app:srcCompat="@drawable/ic_star" />

            <TextView
                android:id="@+id/tvRate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginBottom="16dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imageView7"
                tools:text="4.5" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>

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



res xml item_food_scene.xml:





(Warnings ), ImageView contentDescription. , XML- ( , ).





5: ImageView

  1. ivFood (ImageView );





  2. MotionLayout end;





  3. ivFood (End) (End) ;





  4. ;





  5. layout_height layout_width 300dp.





: ImageView ( , ) , : ( 150dp 300dp).





6: ,

, :





  1. MotionLayout , start end;





  2. Transition;





  3. Play, .





7: CardView

:





  1. cardView (constraintView , , );





  2. MotionLayout end;





  3. cardView ConstraintSet;





  4. Transforms;





  5. alpha 0.





: (end) (start) alpha. ( ).





8:

, :





  1. OnClick ( «+»);





  2. targetId ivFood;





  3. ;





  4. ClickAction toggle.





:

9: RecyclerView activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvMain"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
      
      



10:

package com.mjmanaog.foodbuddy.data.model

import com.mjmanaog.foodbuddy.R

data class FoodModel(
        val title: String,
        val description: String,
        val calories: String,
        val rate: String,
        val imgId: Int
)

val foodDummyData: ArrayList<FoodModel> = arrayListOf(
        FoodModel(
                "  ",
                "   —     .   ,        : ,   .",
                "80 ",
                "4.5",
                R.drawable.img_salmon_salad
        ),
        FoodModel(
                " -",
                " ,           ,       .",
                "80 ",
                "4.5",
                R.drawable.img_chicken
        ),
        FoodModel(
                "    ",
                "   —    .    ,    .         .",
                "80 ",
                "4.5",
                R.drawable.img_chicken_rice
        ),
        FoodModel(
                " ",
                "       ,    (  ),  , ,  , , ,  ,     .",
                "80 ",
                "4.5",
                R.drawable.img_salad
        ),
        FoodModel(
                "  ",
                "   —     .   ,        : ,   .",
                "80 ",
                "4.5",
                R.drawable.img_healthy
        )
)
      
      



11: ViewHolder

. FoodModel





12: RecyclerView

class MainActivity : AppCompatActivity() {
    private var foodAdapter: FoodAdapter = FoodAdapter()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        rvMain.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
        rvMain.adapter = foodAdapter
        foodAdapter.addAll(foodDummyData)
    }
}
      
      



Como resultado de estas simples acciones, obtuvimos la siguiente animación:





No agregué el GIF del artículo porque

Lleva 11 MB.





Algo más

El archivo item_food_scene.xml contiene una descripción de la animación que configuramos. A nadie le molesta que cree y edite animaciones en archivos de escena manualmente.





Espero que el material de este artículo sea útil para alguien. Será genial si aprendes algo nuevo de él.





Gracias por su atención.








All Articles