Русский
Русский
English
Статистика
Реклама

Перевод MotionLayout RecyclerView красивые анимированные списки

В этой статье я расскажу и покажу, как создавать красивые анимированные списки на основе RecyclerView и MotionLayout. Аналогичный метод я использовал в одном из своих проектов.

От переводчика: репозиторий автора статьи - https://github.com/mjmanaog/foodbuddy.
Я его форкнул, чтобы перевести. Возможно, кому-то "русская версия" подойдёт больше.

Что такое MotionLayout?

Если вкратце, то MotionLayout это подкласс ConstraintLayout, который позволяет с помощью XML описывать движения и анимацию расположенных на нём элементов. Подробнее в документации и вот здесь с примерами.

Итак, начнём.

Шаг 1: создадим новый проект

Назовём его, как душе угодно. В качестве активити выберем Empty Activity.

Шаг 2: добавим необходимые зависимости

В gradle-файл приложения добавим:

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

И запустим синхронизацию (Sync Now в правом верхнем углу).

Шаг 3: создадим лэйаут

Наш будущий элемент списка будет выглядеть так:

Элемент списка RecyclerViewЭлемент списка RecyclerView

В папке res/layout создадим файл item_food.

Внутри он выглядит так
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/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.

Как преобразовать ConstraintLayout в MotionLayoutКак преобразовать ConstraintLayout в MotionLayout

Теперь мы можем работать с MotionLayout.

Содержимое файла item_food поменялось
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/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://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/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.modelimport com.mjmanaog.foodbuddy.Rdata 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)    }}

В результате этих несложных действий получилась такая анимация:

GIF из статьи не стал добавлять, потому что

Она вести 11 Мб.

Ещё кое-что

В файле item_food_scene.xml содержится описание анимации, которую мы настроили. Никто не мешает вам создавать и редактировать анимации в файлах сцены вручную.

Надеюсь, материал из этой статьи кому-то окажется полезным. Будет круто, если вы узнаете из неё что-то новое.

Спасибо за внимание.

Источник: habr.com
К списку статей
Опубликовано: 05.04.2021 18:06:45
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка мобильных приложений

Интерфейсы

Разработка под android

Kotlin

Дизайн мобильных приложений

Android

Recyclerview

Motionlayout

Списки

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru