Продвинутый Android в Kotlin 03.2: анимация с помощью MotionLayout

1. Прежде чем начать

Эта лаборатория кода является частью курса Advanced Android in Kotlin. Вы получите максимальную пользу от этого курса, если будете последовательно проходить лабораторные работы по коду, но это не является обязательным. Все лаборатории кода курса перечислены на целевой странице лабораторий кода Advanced Android в Kotlin .

MotionLayout — это библиотека, которая позволяет добавлять насыщенное движение в ваше приложение для Android. Он основан на ConstraintLayout, и позволяет анимировать все, что вы можете создать с помощью ConstraintLayout .

Вы можете использовать MotionLayout для анимации местоположения, размера, видимости, альфа-канала, цвета, высоты, вращения и других атрибутов нескольких представлений одновременно. Используя декларативный XML, вы можете создавать скоординированные анимации, включающие несколько представлений, которые трудно реализовать в коде.

Анимации — отличный способ улучшить качество работы приложения. Вы можете использовать анимацию, чтобы:

  • Показывать изменения — анимация между состояниями позволяет пользователю естественным образом отслеживать изменения в вашем пользовательском интерфейсе.
  • Привлекайте внимание — используйте анимацию, чтобы привлечь внимание к важным элементам пользовательского интерфейса.
  • Создавайте красивые дизайны — эффектные движения в дизайне придают приложениям безупречный вид.

Предварительные условия

Эта лаборатория кода предназначена для разработчиков, имеющих некоторый опыт разработки под Android. Прежде чем пытаться завершить эту кодовую работу, вам следует:

  • Узнайте, как создать приложение с действием и базовым макетом и запустить его на устройстве или в эмуляторе с помощью Android Studio. Ознакомьтесь с ConstraintLayout . Прочтите кодовую лабораторию Constraint Layout , чтобы узнать больше о ConstraintLayout .

Что ты будешь делать

  • Определите анимацию с помощью ConstraintSets и MotionLayout
  • Анимация на основе событий перетаскивания
  • Измените анимацию с помощью KeyPosition
  • Изменение атрибутов с помощью KeyAttribute
  • Запуск анимации с помощью кода
  • Анимация складных заголовков с помощью MotionLayout

Что вам понадобится

  • Android Studio 4.0 (редактор MotionLayout работает только с этой версией Android Studio.)

2. Начало работы

Чтобы загрузить пример приложения, вы можете:

… или клонируйте репозиторий GitHub из командной строки, используя следующую команду:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. Создание анимации с помощью MotionLayout.

Сначала вы создадите анимацию, которая перемещает вид от верхнего начала экрана к нижнему в ответ на щелчки пользователя.

Чтобы создать анимацию из стартового кода, вам понадобятся следующие основные части:

  • MotionLayout, который является подклассом ConstraintLayout . Вы указываете все представления для анимации внутри тега MotionLayout .
  • MotionScene, — XML-файл, описывающий анимацию для MotionLayout.
  • Transition, который является частью MotionScene и определяет продолжительность анимации, триггер и способ перемещения представлений.
  • ConstraintSet , определяющий как начальное , так и конечное ограничения перехода.

Давайте рассмотрим каждый из них по очереди, начиная с MotionLayout .

Шаг 1. Изучите существующий код

MotionLayout — это подкласс ConstraintLayout , поэтому он поддерживает все те же функции при добавлении анимации. Чтобы использовать MotionLayout , вы добавляете представление MotionLayout , в котором вы бы использовали ConstraintLayout.

  1. В res/layout откройте activity_step1.xml. Здесь у вас есть ConstraintLayout с одним ImageView звезды, внутри которого применен оттенок.

Activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

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

Этот ConstraintLayout не имеет никаких ограничений, поэтому, если вы запустите приложение сейчас, вы увидите, что звездочки отображаются без ограничений, а это означает, что они будут расположены в неизвестном месте. Android Studio выдаст вам предупреждение об отсутствии ограничений.

Шаг 2. Преобразование в макет движения

Чтобы анимировать с помощью MotionLayout, вам необходимо преобразовать ConstraintLayout в MotionLayout .

Чтобы ваш макет мог использовать сцену движения, он должен указывать на нее.

  1. Для этого откройте область конструктора. В Android Studio 4.0 вы открываете область дизайна, используя значок разделения или дизайна в правом верхнем углу при просмотре XML-файла макета.

a2beea710c2decb7.png

  1. Открыв область конструктора, щелкните правой кнопкой мыши предварительный просмотр и выберите «Преобразовать в MotionLayout» .

4fa936a98a8393b9.png

При этом тег ConstraintLayout заменяется тегом MotionLayout и добавляется motion:layoutDescription к тегу MotionLayout , который указывает на @xml/activity_step1_scene.

Activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

Сцена движения — это отдельный XML-файл, описывающий анимацию в MotionLayout .

Как только вы преобразуетесь в MotionLayout , в области дизайна отобразится редактор движения.

66d0e80d5ab4daf8.png

В редакторе движений есть три новых элемента пользовательского интерфейса:

  1. Обзор – это модальный выбор, который позволяет выбирать различные части анимации. На этом изображении выбран start ConstraintSet . Вы также можете выбрать переход между start и end , щелкнув стрелку между ними.
  2. Раздел . Под обзором находится окно раздела, которое меняется в зависимости от выбранного в данный момент элемента обзора. На этом изображении start информация ConstraintSet отображается в окне выбора.
  3. Атрибут — панель атрибутов отображает и позволяет редактировать атрибуты текущего выбранного элемента либо в окне обзора, либо в окне выбора. На этом изображении показаны атрибуты start ConstraintSet .

Шаг 3. Определите начальные и конечные ограничения.

Все анимации можно определить с точки зрения начала и конца. Начало описывает, как выглядит экран до анимации, а конец описывает, как экран выглядит после завершения анимации. MotionLayout отвечает за определение того, как анимировать между начальным и конечным состоянием (с течением времени).

MotionScene использует тег ConstraintSet для определения начального и конечного состояний. ConstraintSet — это то, как это звучит: набор ограничений, которые можно применять к представлениям. Сюда входят ограничения ширины, высоты и ConstraintLayout . Он также включает в себя некоторые атрибуты, такие как alpha . Он не содержит самих представлений, а только ограничения этих представлений.

Любые ограничения, указанные в ConstraintSet переопределяют ограничения, указанные в файле макета. Если вы определяете ограничения как в макете, так и в MotionScene , применяются только ограничения в MotionScene .

На этом этапе вы ограничите вид звезды так, чтобы он начинался с верхнего начала экрана и заканчивался в нижнем конце экрана.

Вы можете выполнить этот шаг либо с помощью редактора движения, либо напрямую отредактировав текст activity_step1_scene.xml .

  1. Выберите start ConstraintSet на панели обзора.

6e57661ed358b860.png

  1. На панели выбора выберите red_star . В настоящее время отображается источник layout — это означает, что он не ограничен этим ConstraintSet . Используйте значок карандаша в правом верхнем углу, чтобы создать ограничение.

f9564c574b86ea8.gif

  1. Убедитесь, что red_star отображает источник start , когда на панели обзора выбран start ConstraintSet .
  2. На панели Attributes с выбранным red_star в start ConstraintSet добавьте Constraint вверху и начните с нажатия синих кнопок + .

2fce076cd7b04bd.png

  1. Откройте xml/activity_step1_scene.xml чтобы просмотреть код, сгенерированный редактором движения для этого ограничения.

Activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

ConstraintSet имеет id @id/start и определяет все ограничения, которые применяются ко всем представлениям в MotionLayout . Поскольку этот MotionLayout имеет только одно представление, ему нужен только один Constraint .

Constraint внутри ConstraintSet указывает идентификатор представления, которое оно ограничивает, @id/red_star определенное в activity_step1.xml . Важно отметить, что теги Constraint определяют только ограничения и информацию о макете. Тег Constraint не знает, что он применяется к ImageView .

Это ограничение определяет высоту, ширину и два других ограничения, необходимые для ограничения представления red_star верхним началом его родительского элемента.

  1. Выберите end ConstraintSet на панели обзора.

346e1248639b6f1e.png

  1. Выполните те же действия, что и раньше, чтобы добавить Constraint для red_star в end ConstraintSet .
  2. Чтобы использовать редактор движения для выполнения этого шага, добавьте ограничение bottom и end , нажав синие кнопки + .

fd33c779ff83c80a.png

  1. Код в XML выглядит следующим образом:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Как и @id/start , этот ConstraintSet имеет единственное Constraint для @id/red_star . На этот раз он ограничивает его нижним краем экрана.

Вам не обязательно называть их @id/start и @id/end , но это удобно.

Шаг 4. Определите переход

Каждая MotionScene также должна включать хотя бы один переход. Переход определяет каждую часть анимации от начала до конца.

Для перехода необходимо указать начальный и конечный ConstraintSet для перехода. Переход также может указывать, как изменить анимацию другими способами, например, как долго выполнять анимацию или как анимировать путем перетаскивания представлений.

  1. Редактор движения по умолчанию создал для нас переход при создании файла MotionScene. Откройте activity_step1_scene.xml , чтобы увидеть сгенерированный переход.

Activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

Это все, что нужно MotionLayout для создания анимации. Рассматривая каждый атрибут:

  • constraintSetStart будет применено к представлениям при запуске анимации.
  • constraintSetEnd будет применено к представлениям в конце анимации.
  • duration указывает, как долго должна длиться анимация в миллисекундах.

Затем MotionLayout определит путь между начальным и конечным ограничениями и анимирует его в течение указанной продолжительности.

Шаг 5. Предварительный просмотр анимации в редакторе движений.

dff9ecdc1f4a0740.gif

Анимация: видео воспроизведения предварительного просмотра перехода в редакторе движений.

  1. Откройте редактор движения и выберите переход, щелкнув стрелку между start и end на панели обзора.

1dc541ae8c43b250.png

  1. На панели выбора отображаются элементы управления воспроизведением и полоса прокрутки, когда выбран переход. Нажмите «Воспроизвести» или перетащите текущую позицию, чтобы просмотреть анимацию.

a0fd2593384dfb36.png

Шаг 6. Добавьте обработчик кликов

Вам нужен способ запустить анимацию. Один из способов сделать это — заставить MotionLayout реагировать на события щелчка по @id/red_star .

  1. Откройте редактор движения и выберите переход, щелкнув стрелку между началом и концом на панели обзора.

b6f94b344ce65290.png

  1. Нажмите 699f7ae04024ccf6.png Создайте обработчик щелчков или пролистывания на панели инструментов для панели обзора. Это добавляет обработчик, который начнет переход.
  2. Выберите «Обработчик кликов» во всплывающем окне.

ccf92d06335105fe.png

  1. Измените вид для щелчка на red_star .

b0d3f0c970604f01.png

  1. Нажмите « Добавить». Обработчик щелчков представлен маленькой точкой в ​​редакторе «Переход в движении».

cec3913e67fb4105.png

  1. Выбрав переход на панели обзора, добавьте атрибут toggle clickAction к обработчику OnClick, который вы только что добавили на панели атрибутов.

9af6fc60673d093d.png

  1. Откройте activity_step1_scene.xml , чтобы увидеть код, сгенерированный редактором движений.

Activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

Transition сообщает MotionLayout запускать анимацию в ответ на события щелчка с помощью тега <OnClick> . Рассматривая каждый атрибут:

  • targetId — это представление для отслеживания кликов.
  • clickAction toggle будет переключаться между начальным и конечным состоянием при нажатии. Другие варианты clickAction вы можете посмотреть в документации .
  1. Запустите свой код, нажмите «Шаг 1» , затем нажмите красную звездочку и посмотрите анимацию!

Шаг 5. Анимация в действии

Запустите приложение! Вы должны увидеть, как анимация запускается, когда вы нажимаете на звезду.

7ba88af963fdfe10.gif

Завершенный файл сцены движения определяет один Transition , который указывает на начальный и конечный ConstraintSet .

В начале анимации ( @id/start ) значок звездочки ограничен верхней частью экрана. В конце анимации ( @id/end ) значок звездочки ограничивается нижней частью экрана.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. Анимация на основе событий перетаскивания

На этом этапе вы создадите анимацию, которая будет реагировать на событие перетаскивания пользователя (когда пользователь проводит пальцем по экрану) для запуска анимации. MotionLayout поддерживает отслеживание событий касания для перемещения представлений, а также основанные на физике жесты для плавности движения.

Шаг 1. Проверьте исходный код

  1. Для начала откройте файл макета activity_step2.xml , в котором уже существует MotionLayout . Взгляните на код.

Activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Этот макет определяет все виды анимации. Три значка звездочки не ограничены в макете, поскольку они будут анимированы в сцене движения.

К TextView в титрах применяются ограничения, поскольку он остается в одном и том же месте на протяжении всей анимации и не изменяет никаких атрибутов.

Шаг 2. Анимируйте сцену

Как и в случае с предыдущей анимацией, анимация будет определяться начальным и конечным ConstraintSet, а также Transition .

Определите начальный ConstraintSet

  1. Откройте сцену движения xml/step2.xml чтобы определить анимацию.
  2. Добавьте ограничения для начального ограничения start . Вначале все три звезды расположены по центру нижней части экрана. Правая и левая звезды имеют значение alpha 0.0 , что означает, что они полностью прозрачны и скрыты.

шаг2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

В этом ConstraintSet вы указываете одно Constraint для каждой звезды. Каждое ограничение будет применено MotionLayout в начале анимации.

Каждый звездный вид центрируется в нижней части экрана с использованием ограничений начала, конца и низа. Две звезды @id/left_star и @id/right_star имеют дополнительное значение альфа, которое делает их невидимыми и которое будет применено в начале анимации.

Наборы ограничений start и end определяют начало и конец анимации. Ограничение начала, например motion:layout_constraintStart_toStartOf , ограничит начало представления началом другого представления. Поначалу это может сбить с толку, поскольку имя start используется для обоих , и оба они используются в контексте ограничений. Чтобы подчеркнуть различие, start в layout_constraintStart относится к «началу» представления, которое находится слева в языке слева направо и справа в языке справа налево. Набор start ограничений относится к началу анимации.

Определите конечный ConstraintSet

  1. Определите конечное ограничение, чтобы использовать цепочку для размещения всех трех звезд вместе под @id/credits . Кроме того, будет установлено конечное значение alpha левой и правой звезд равным 1.0 .

шаг2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

Конечным результатом является то, что виды будут распространяться вверх от центра по мере их анимации.

Кроме того, поскольку свойство alpha установлено для @id/right_start и @id/left_star в обоих ConstraintSets , оба представления будут постепенно проявляться по мере продвижения анимации.

Анимация на основе свайпа пользователя

MotionLayout может отслеживать события перетаскивания пользователя или пролистывания для создания анимации «броска», основанной на физике. Это означает, что виды будут продолжать работать, если пользователь их бросит, и будут замедляться, как физический объект, катящийся по поверхности. Вы можете добавить этот тип анимации с помощью тега OnSwipe в Transition .

  1. Замените TODO для добавления тега OnSwipe на <OnSwipe motion:touchAnchorId="@id/red_star" /> .

шаг2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe содержит несколько атрибутов, наиболее важным из которых является touchAnchorId .

  • touchAnchorId — это отслеживаемое представление, которое перемещается в ответ на прикосновение. MotionLayout будет держать это представление на том же расстоянии от пальца, который проводит пальцем.
  • touchAnchorSide определяет, какую сторону изображения следует отслеживать. Это важно для представлений, которые меняют размер, следуют по сложным траекториям или имеют одну сторону, которая движется быстрее другой.
  • dragDirection определяет, какое направление имеет значение для этой анимации (вверх, вниз, влево или вправо).

Когда MotionLayout прослушивает события перетаскивания, прослушиватель будет зарегистрирован в представлении MotionLayout , а не в представлении, указанном touchAnchorId . Когда пользователь начинает жест в любом месте экрана, MotionLayout сохраняет расстояние между пальцем и touchAnchorSide представления touchAnchorId постоянным. Например, если они касаются на расстоянии 100 dp от стороны привязки, MotionLayout будет удерживать эту сторону на расстоянии 100 dp от пальца на протяжении всей анимации.

Попробуйте это

  1. Запустите приложение еще раз и откройте экран шага 2. Вы увидите анимацию.
  2. Попробуйте «подбросить» или отпустить палец в середине анимации, чтобы узнать, как MotionLayout отображает анимацию, основанную на физике жидкости!

fefcdd690a0dcaec.gif

MotionLayout может анимировать самые разные проекты, используя функции ConstraintLayout для создания богатых эффектов.

В этой анимации все три представления вначале располагаются относительно родительского элемента внизу экрана. В конце три представления располагаются в цепочке относительно @id/credits .

Несмотря на столь разные макеты, MotionLayout создаст плавную анимацию между началом и концом.

5. Изменение пути

На этом этапе вы создадите анимацию, которая следует по сложному пути во время анимации и анимирует титры во время движения. MotionLayout может изменить путь, по которому будет проходить представление между началом и концом, с помощью KeyPosition .

Шаг 1. Изучите существующий код

  1. Откройте layout/activity_step3.xml и xml/step3.xml чтобы увидеть существующий макет и сцену движения. ImageView и TextView отображают луну и текст титров.
  2. Откройте файл сцены движения ( xml/step3.xml ). Вы видите, что определен Transition от @id/start к @id/end . Анимация перемещает изображение луны из нижнего левого угла экрана в нижний правый с помощью двух ConstraintSets . Текст титров плавно меняется от alpha="0.0" до alpha="1.0" по мере движения луны.
  3. Запустите приложение сейчас и выберите «Шаг 3» . Вы увидите, что луна следует линейному пути (или прямой линии) от начала до конца, когда вы нажмете на луну.

Шаг 2. Включите отладку пути

Прежде чем добавлять дугу к движению Луны, полезно включить отладку пути в MotionLayout .

Чтобы облегчить разработку сложных анимаций с помощью MotionLayout , вы можете нарисовать путь анимации каждого представления. Это полезно, когда вы хотите визуализировать анимацию, а также для точной настройки мелких деталей движения.

  1. Чтобы включить пути отладки, откройте layout/activity_step3.xml и добавьте motion:motionDebug="SHOW_PATH" в тег MotionLayout .

Activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

После включения отладки пути при повторном запуске приложения вы увидите пути всех представлений, визуализированные пунктирной линией.

23bbb604f456f65c.png

  • Круги обозначают начальную или конечную позицию одного вида.
  • Линии представляют собой путь одного вида.
  • Ромбики представляют собой KeyPosition , изменяющую путь.

Например, в этой анимации средний круг — это позиция текста титров.

Шаг 3. Измените путь

Все анимации в MotionLayout определяются начальным и конечным ConstraintSet , который определяет, как выглядит экран до начала анимации и после ее завершения. По умолчанию MotionLayout строит линейный путь (прямую линию) между начальной и конечной позицией каждого вида, который меняет положение.

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

  1. Откройте xml/step3.xml и добавьте в сцену KeyPosition . Тег KeyPosition размещается внутри тега Transition .

eae4dae9a12d0410.png

шаг3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSet является дочерним элементом Transition и представляет собой набор всех KeyFrames , таких как KeyPosition , которые должны применяться во время перехода.

Поскольку MotionLayout вычисляет путь для луны между началом и концом, он изменяет путь на основе KeyPosition , указанного в KeyFrameSet . Вы можете увидеть, как это изменяет путь, снова запустив приложение.

KeyPosition имеет несколько атрибутов, которые описывают, как он изменяет путь. Наиболее важные из них:

  • framePosition — это число от 0 до 100. Оно определяет, когда в анимации следует применять эту KeyPosition : 1 — это 1% анимации, а 99 — 99% анимации. Поэтому, если значение равно 50, вы применяете его прямо посередине.
  • motionTarget — это представление, для которого этот KeyPosition изменяет путь.
  • keyPositionType — это то, как эта KeyPosition изменяет путь. Это может быть parentRelative , pathRelative или deltaRelative (как описано в следующем шаге).
  • percentX | percentY — это степень изменения пути в framePosition (значения от 0,0 до 1,0, разрешены отрицательные значения и значения > 1).

Вы можете подумать об этом так: «В framePosition измените путь motionTarget, motionTarget его на percentX или percentY в соответствии с координатами, определенными keyPositionType ».

По умолчанию MotionLayout скругляет любые углы, возникающие при изменении пути. Если вы посмотрите на только что созданную анимацию, вы увидите, что луна следует по изогнутой траектории на изгибе. Для большинства анимаций это то, что вам нужно, а если нет, вы можете указать атрибут curveFit чтобы настроить его.

Попробуйте это

Если вы запустите приложение еще раз, вы увидите анимацию этого шага.

46b179c01801f19e.gif

Луна следует по дуге, потому что она проходит через KeyPosition , указанную в Transition .

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

Вы можете прочитать это KeyPosition так: «В framePosition 50 (в середине анимации) измените путь motionTarget @id/moon переместив его на 50% Y (на полпути вниз по экрану) в соответствии с координатами, определенными parentRelative (весь MotionLayout )"

Итак, в середине анимации луна должна пройти через KeyPosition , расположенную на 50 % ниже экрана. Это KeyPosition вообще не изменяет движение X, поэтому луна по-прежнему будет двигаться от начала до конца по горизонтали. MotionLayout определит плавный путь, проходящий через эту KeyPosition , перемещаясь между началом и концом.

Если присмотреться, текст титров ограничен положением Луны. Почему он не движется вертикально?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

Оказывается, даже если вы меняете путь, по которому движется Луна, начальное и конечное положения Луны вообще не перемещают ее по вертикали. KeyPosition не изменяет начальную или конечную позицию, поэтому текст титров ограничен конечным положением луны.

Если вы хотите, чтобы титры перемещались вместе с луной, вы можете добавить к титрам KeyPosition или изменить начальные ограничения @id/credits .

В следующем разделе вы познакомитесь с различными типами keyPositionType в MotionLayout .

6. Понимание типа keyPositionType

На последнем шаге вы использовали тип keyPosition для parentRelative , чтобы сместить путь на 50% экрана. Атрибут keyPositionType определяет , как MotionLayout будет изменять путь в соответствии с percentX или percentY

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

Возможны три различных типа keyPosition : parentRelative , pathRelative и deltaRelative . Указание типа изменит систему координат, по которой рассчитываются percentX и percentY

Что такое система координат?

Система координат дает возможность указать точку в пространстве. Они также полезны для описания положения на экране.

Системы координат MotionLayout представляют собой декартову систему координат . Это означает, что у них есть оси X и Y, определяемые двумя перпендикулярными линиями. Ключевое различие между ними заключается в том, где на экране проходит ось X (ось Y всегда перпендикулярна оси X).

Все системы координат в MotionLayout используют значения от 0.0 до 1.0 по осям X и Y. Они допускают отрицательные значения и значения больше 1.0 . Так, например, значение percentX , равное -2.0 будет означать, что нужно дважды пойти в направлении, противоположном оси X.

Если все это слишком похоже на урок алгебры, взгляните на картинки ниже!

родительОтносительные координаты

a7b7568d46d9dec7.png

keyPositionType parentRelative использует ту же систему координат, что и экран. Он определяет (0, 0) в верхнем левом углу всего MotionLayout и (1, 1) в правом нижнем углу.

Вы можете parentRelative всякий раз, когда хотите создать анимацию, которая перемещается по всему MotionLayout — например, дугу луны в этом примере.

Однако, если вы хотите изменить путь относительно движения, например, сделать его немного изогнутым, лучше выбрать две другие системы координат.

deltaОтносительные координаты

5680bf553627416c.png

Дельта — это математический термин, обозначающий изменение, поэтому deltaRelative — это способ сказать «изменение относительное». В координатах deltaRelative (0,0) — это начальная позиция представления, а (1,1) — конечная позиция. Оси X и Y выровнены по экрану.

Ось X всегда горизонтальна на экране, а ось Y всегда вертикальна на экране. По сравнению с parentRelative основное отличие состоит в том, что координаты описывают только часть экрана, в которой будет перемещаться представление.

deltaRelative — отличная система координат для изолированного управления горизонтальным или вертикальным движением. Например, вы можете создать анимацию, которая завершает только вертикальное (Y) движение при 50 % и продолжает анимацию по горизонтали (X).

p athОтносительные координаты

f3aaadaac8b4a93f.png

Последняя система координат в MotionLayoutpathRelative . Он сильно отличается от двух других, поскольку ось X следует за траекторией движения от начала до конца. Итак, (0,0) — начальная позиция, а (1,0) — конечная позиция.

Зачем вам это нужно? На первый взгляд это довольно удивительно, тем более что эта система координат даже не выровнена с системой координат экрана.

Оказывается, pathRelative действительно полезен в нескольких случаях.

  • Ускорение, замедление или остановка просмотра во время части анимации. Поскольку измерение X всегда будет точно соответствовать пути, по которому движется представление, вы можете использовать pathRelative KeyPosition чтобы изменить, в какой framePosition достигается конкретная точка на этом пути. Таким образом, KeyPosition с framePosition="50" с percentX="0.1" приведет к тому, что анимация займет 50% времени, чтобы пройти первые 10% движения.
  • Добавление тонкой дуги к пути. Поскольку размер Y всегда перпендикулярен движению, изменение Y изменит траекторию кривой относительно общего движения.
  • Добавление второго измерения, когда deltaRelative не работает. Для полностью горизонтального и вертикального движения deltaRelative создаст только одно полезное измерение. Однако pathRelative всегда будет создавать пригодные для использования координаты X и Y.

На следующем шаге вы узнаете, как строить еще более сложные пути, используя более одного KeyPosition .

7. Создание сложных дорожек

Глядя на анимацию, которую вы построили на последнем шаге, она создает плавную кривую, но форма может быть более «луной».

Измените путь с несколькими элементами клавиатуры

MotionLayout может изменить путь дальше, определив столько KeyPosition , сколько необходимо, чтобы получить любое движение. Для этой анимации вы построите дугу, но вы можете заставить Луну прыгать вверх и вниз в середине экрана, если хотите.

  1. Откройте xml/step4.xml . Вы видите, что у него есть те же представления и KeyFrame , который вы добавили на последнем шаге.
  2. Чтобы завершить верхнюю часть кривой, добавьте еще две KeyPositions в путь @id/moon , один раз перед тем, как он достигнет вершины, и одна после.

500B5AC2DB48EF87.PNG

Step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

Эти KeyPositions будут применяться на 25% и 75% пути через анимацию и приведут к тому @id/moon пройдет через путь, который на 60% от верхней части экрана. В сочетании с существующей KeyPosition на 50%это создает гладкую дугу для следования Луны.

В MotionLayout вы можете добавить столько KeyPositions , сколько вам нужно, чтобы получить нужный путь движения. MotionLayout будет применять каждую KeyPosition в указанном framePosition и выяснить, как создать плавное движение, которое проходит все KeyPositions .

Попробуйте это

  1. Запустите приложение снова. Перейдите к шагу 4 , чтобы увидеть анимацию в действии. Когда вы нажимаете на Луну, она следует по пути от начала до конца, проходя каждую KeyPosition , которая была указана в KeyFrameSet .

Изучите самостоятельно

Прежде чем перейти к другим типам KeyFrame , попробуйте добавить еще несколько KeyPositions в KeyFrameSet , чтобы увидеть, какие эффекты вы можете создать, просто используя KeyPosition .

Вот один пример, показывающий, как построить сложный путь, который движется вперед и назад во время анимации.

cd9faaffde3dfef.png

Step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

Как только вы закончите исследовать KeyPosition , на следующем шаге вы переходите к другим типам KeyFrames .

8. Изменение атрибутов во время движения

Построение динамической анимации часто означает изменение size , rotation или alpha видов по мере развития анимации. MotionLayout поддерживает анимирование многих атрибутов в любом представлении, используя KeyAttribute .

На этом этапе вы будете использовать KeyAttribute , чтобы сделать масштаб луны и вращаться. Вы также будете использовать KeyAttribute , чтобы задержать внешний вид текста, пока Луна почти завершит свое путешествие.

Шаг 1: Изменить размер и повернуть с помощью KeyAttribute

  1. Откройте xml/step5.xml , которая содержит ту же анимацию, которую вы построили на последнем шаге. Для разнообразия этот экран использует другое пространство в качестве фона.
  2. Чтобы луна расширилась по размеру и повернуте, добавьте два тега KeyAttribute в KeyFrameSet At keyFrame="50" и keyFrame="100"

BBAE524A2898569.png

Step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

Эти KeyAttributes применяются на 50% и 100% анимации. Первый KeyAttribute при 50% произойдет в верхней части дуги, и приведет к удвоению вида, а также повернуть -360 градусов (или один полный круг). Второй KeyAttribute завершит второе вращение до -720 градусов (два полных круга) и уменьшит размер обратно до регулярного, поскольку scaleX и scaleY значений по умолчанию до 1,0.

Так же, как KeyPosition , KeyAttribute использует framePosition и motionTarget чтобы указать, когда применить KeyFrame и какое представление для изменения. MotionLayout будет интерполировать между KeyPositions для создания жидкости анимации.

KeyAttributes поддерживают атрибуты , которые могут применяться ко всем представлениям. Они поддерживают изменение основных атрибутов, таких как visibility , alpha или elevation . Вы также можете изменить вращение, как вы здесь делаете, повернуть в три измерения с rotateX и rotateY , масштабируйте размер с помощью scaleX и scaleY , или перевести положение вида в X, Y или Z.

Шаг 2: Задержка появления кредитов

Одна из целей этого шага - обновить анимацию, чтобы текст титров не появлялся до тех пор, пока анимация в основном не будет завершена.

  1. Чтобы отложить появление кредитов, определите еще один KeyAttribute , который гарантирует, что alpha останется 0 до keyPosition="85" . MotionLayout по -прежнему будет плавно переходить от 0 до 100 альфой, но он сделает это за последние 15% анимации.

Step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

Этот KeyAttribute сохраняет alpha @id/credits в 0,0 для первых 85% анимации. Поскольку он начинается с альфа -0, это означает, что это будет невидимо для первых 85% анимации.

Конечным эффектом этого KeyAttribute является то, что кредиты появляются к концу анимации. Это дает появление того, что они координируются с луной, оседающей в правом углу экрана.

Задерживая анимации на одном представлении, в то время как другой вид движется так, вы можете создавать впечатляющие анимации, которые чувствуют себя динамичными для пользователя.

Попробуйте это

  1. Запустите приложение снова и перейдите к шагу 5 , чтобы увидеть анимацию в действии. Когда вы нажмете на Луну, он будет следовать по пути от начала до конца, проходя через каждый KeyAttribute , который был указан в KeyFrameSet .

2f4bfdd681c1fa98.gif

Поскольку вы поворачиваете луну два полных круга, теперь она сделает двойную спину, и титры откладывают их внешний вид, пока анимация не закончится.

Изучите самостоятельно

Прежде чем перейти к окончательному типу KeyFrame , попробуйте изменить другие стандартные атрибуты в KeyAttributes . Например, попробуйте изменить rotation на rotationX , чтобы увидеть, какую анимацию он производит.

Вот список стандартных атрибутов, которые вы можете попробовать:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9. Изменение пользовательских атрибутов

Богатые анимации включают изменение цвета или других атрибутов представления. В то время как MotionLayout может использовать KeyAttribute для изменения любого из стандартных атрибутов, перечисленных в предыдущей задаче, вы используете CustomAttribute для указания любого другого атрибута.

CustomAttribute можно использовать для установки любого значения, которое имеет сеттер. Например, вы можете установить FounalColor в представлении, используя CustomAttribute . MotionLayout будет использовать отражение, чтобы найти сеттер, а затем назовите его неоднократно, чтобы оживить представление.

На этом этапе вы будете использовать CustomAttribute , чтобы установить атрибут colorFilter на Луне, чтобы построить анимацию, показанную ниже.

5FB6792126A09FDA.GIF

Определите пользовательские атрибуты

  1. Чтобы начать открыть xml/step6.xml , которая содержит ту же анимацию, которую вы построили на последнем шаге.
  2. Чтобы заменить цвета луны, добавьте два KeyAttribute с CustomAttribute в KeyFrameSet At keyFrame="0" , keyFrame="50" и keyFrame="100".

214699d5fdd956da.png

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

Вы добавляете CustomAttribute в KeyAttribute . CustomAttribute будет применен в framePosition , указанном в KeyAttribute .

Внутри CustomAttribute вы должны указать attributeName и одно значение для установки.

  • motion:attributeName - это имя сеттера, который будет вызван этим пользовательским атрибутом. В этом примере будет вызван setColorFilter на Drawable .
  • motion:custom*Value - это пользовательское значение типа, отмеченного в имени, в этом примере пользовательское значение является указанным цветом.

Пользовательские значения могут иметь любой из следующих типов:

  • Цвет
  • Целое число
  • Плавать
  • Нить
  • Измерение
  • логическое значение

Используя этот API, MotionLayout может оживить все, что обеспечивает сеттер для любого представления.

Попробуйте это

  1. Запустите приложение снова и перейдите к шагу 6 , чтобы увидеть анимацию в действии. Когда вы нажмете на Луну, он будет следовать по пути от начала до конца, проходя через каждый KeyAttribute , который был указан в KeyFrameSet .

5FB6792126A09FDA.GIF

Когда вы добавляете больше KeyFrames , MotionLayout изменяет путь Луны с прямой линии на сложную кривую, добавляя двойной обратный флип, изменение размера и изменение цвета в середине анимации.

В реальных анимациях вы часто оживляете несколько просмотров одновременно, контролируя их движение по разным путям и скоростям. Указав свой KeyFrame для каждого представления, можно хореографировать богатые анимации, которые оживляют несколько представлений с помощью MotionLayout .

10. События перетаскивания и сложные пути

На этом этапе вы исследуете, используя OnSwipe со сложными путями. До сих пор анимация Луны была вызвана прослушивателем OnClick и работает на фиксированную продолжительность.

Контроль анимаций, которые имеют сложные пути, используя OnSwipe , например, анимация Moon, которую вы создали в последних нескольких шагах, требует понимания того, как работает OnSwipe .

Шаг 1: Исследуйте поведение в сфере

  1. Откройте xml/step7.xml и найдите существующее объявление OnSwipe .

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. Запустите приложение на своем устройстве и перейдите к шагу 7 . Посмотрите, сможете ли вы создать плавную анимацию, перетаскивая луну вдоль пути дуги.

Когда вы запускаете эту анимацию, она выглядит не очень хорошо. После того, как луна достигает вершины дуги, она начинает прыгать вокруг.

ED96E3674854A548.gif

Чтобы понять ошибку, подумайте, что происходит, когда пользователь касается чуть ниже вершины дуги. Поскольку тег OnSwipe имеет motion:touchAnchorSide="bottom" MotionLayout попытается провести расстояние между пальцем и нижней частью представления постоянной на протяжении всей анимации.

Но, поскольку нижняя часть луны не всегда идет в одном направлении, он поднимается, затем возвращается, MotionLayout не знает, что делать, когда пользователь только что прошел верхнюю часть дуги. Чтобы рассмотреть это, поскольку вы отслеживаете дно Луны, где его следует размещать, когда пользователь здесь касается?

56CD575C5C77EDDD.PNG

Шаг 2: Используйте правую сторону

Чтобы избежать таких ошибок, важно всегда выбирать touchAnchorId и touchAnchorSide , которые всегда прогрессируют в одном направлении на протяжении всего времени всей анимации.

В этой анимации как right , так и left сторона Луны будут развиваться через экран в одном направлении.

Тем не менее, как bottom , так и top будут обращать внимание на направление. Когда OnSwipe пытается отследить их, это будет запутаться, когда их направление изменится.

  1. Чтобы эта анимация следила за событиями прикосновения, измените touchAnchorSide right .

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

Шаг 3: Используйте Dragdection

Вы также можете объединить dragDirection с touchAnchorSide чтобы сделать боковую дорожку другим направлением, чем обычно. Все еще важно, чтобы touchAnchorSide прогрессировал только в одном направлении, но вы можете сказать MotionLayout , в каком направлении отслеживать. Например, вы можете сохранить touchAnchorSide="bottom" , но добавить dragDirection="dragRight" . Это приведет к тому, что MotionLayout отслеживает положение нижнего вида, но рассматривает его местонахождение только при движении вправо (он игнорирует вертикальное движение). Таким образом, хотя дно идет вверх и вниз, оно все равно будет правильно оживить с помощью OnSwipe .

  1. OnSwipe , чтобы правильно отслеживать движение Луны.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

Попробуйте это

  1. Запустите приложение снова и попробуйте перетащить луну по всему пути. Несмотря на то, что это следует за сложной дугой, MotionLayout сможет прогрессировать анимацию в ответ на события смахивания.

5458dff382261427.gif

11. Запуск движения с кодом

MotionLayout может использоваться для создания богатых анимаций при использовании с CoordinatorLayout . На этом этапе вы построите складной заголовок, используя MotionLayout .

Шаг 1: Исследуйте существующий код

  1. Чтобы начать, открыть layout/activity_step8.xml .
  2. В layout/activity_step8.xml вы видите, что работающий CoordinatorLayout и AppBarLayout уже создан.

Activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

В этом макете используется CoordinatorLayout для обмена информацией о прокрутке между NestedScrollView и AppBarLayout . Таким образом, когда вложенный прокручивается NestedScrollView , он расскажет AppBarLayout об изменении. Вот как вы реализуете панель инструментов, подобную этой, на Android - прокрутка текста будет «скоординирована» с обезжиривающим заголовком.

Сцена движения, на которую указывает @id/motion_layout , похожа на сцену движения на последнем шаге. Тем не менее, декларация OnSwipe была удалена, чтобы позволить ему работать с CoordinatorLayout .

  1. Запустите приложение и перейдите к шагу 8 . Вы видите это, когда вы прокручиваете текст, луна не движется.

Шаг 2: Сделайте прокрутку MotionLayout

  1. Чтобы сделать прокрутку представления MotionLayout , как только NestedScrollView прокрутки CrollView добавьте motion:minHeight и motion:layout_scrollFlags в MotionLayout .

Activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. Запустите приложение снова и перейдите к шагу 8 . Вы видите, что MotionLayout рушится, когда вы прокручиваете вверх. Тем не менее, анимация пока не прогрессирует на основе поведения прокрутки.

Шаг 3: Переместите движение с кодом

  1. Open Step8Activity.kt . Отредактируйте функцию coordinateMotion() , чтобы рассказать MotionLayout об изменениях в положении прокрутки.

Step8activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

Этот код зарегистрирует OnOffsetChangedListener , который будет вызывать каждый раз, когда пользователь прокручивает с текущим смещением прокрутки.

MotionLayout поддерживает их переход, установив свойство Progress. Чтобы преобразовать между verticalOffset и процентным прогрессом, разделите на общий диапазон прокрутки.

Попробуйте это

  1. Разверните приложение снова и запустите анимацию шага 8 . Вы видите, что MotionLayout будет развивать анимацию на основе позиции прокрутки.

EE5CE4D9E33A59CA.GIF

Можно создать пользовательские динамические анимации панели инструментов с использованием MotionLayout . Используя последовательность KeyFrames вы можете достичь очень смелых эффектов.

12. Поздравляю

Этот CodeLab охватывал базовый API MotionLayout .

Чтобы увидеть больше примеров MotionLayout на практике, ознакомьтесь с официальной выборкой . И обязательно ознакомьтесь с документацией !

Узнать больше

MotionLayout поддерживает еще больше функций, не рассматриваемых в этом CodeLab, таких как KeyCycle, который позволяет вам контролировать пути или атрибуты с повторяющимися циклами, и KeyTimeCycle, который позволяет вам оживить в зависимости от времени. Проверьте образцы для примеров каждого.

Ссылки на другие коделабы в этом курсе см. Веденный Android на целевой странице Kotlin CodeLabs .