Android studio анимация задержка

Полный список

В этом уроке мы:

— рассмотрим анимацию View-компонентов

Перед серьезными темами я решил все таки рассмотреть еще одну интересную и, на мой взгляд, несложную тему. Правда рассмотрю я только вершину и в дебри не полезу. Тема – анимация. Мы научимся проделывать следующие трансформации с обычными View-компонентами:
— менять прозрачность
— менять размер
— перемещать
— поворачивать

Создадим проект:

Project name: P0201_SimpleAnimation
Build Target: Android 2.3.3
Application name: SimpleAnimation
Package name: ru.startandroid.develop.p0201simpleanimation
Create Activity: MainActivity

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

В нашем проекте есть папка res. Надо в ней создать папку anim. Сделать это можно, например, так: правой кнопкой на res и в меню выбираем New -> Folder. В папке anim надо создать файлы. Делается это аналогично: правой кнопкой на anim и в меню выбираем New -> File. В этих файлах будем конфигурировать анимацию.

Создаем следующие файлы в папке res/anim:

Имя файла: myalpha.xml

Описание трансформации: меняется прозрачность с 0 до 1 в течение трех секунд.

Имя файла: myscale.xml

Описание трансформации: изменение размера с 0.1 от оригинальной ширины и высоты до 1. Точка, относительно которой будет производиться масштабирование, лежит ровно посередине объекта (pivotX, pivotY). Продолжительность – 3 сек.

Имя файла: mytrans.xml

Описание трансформации: перемещение с -150 относительно текущей позиции по оси X и -200 по оси Y в текущую позицию (0,0). Продолжительность – 3 сек.

Имя файла: myrotate.xml

Описание трансформации: поворот относительно левого верхнего угла (т.к. не указаны pivotX, pivotY) на 360 градусов в течение трех секунд

Имя файла: mycombo.xml

Описание трансформации: одновременно увеличение размера и вращение в течение трех секунд. Обратите внимание, для комбинации трансформ использован тег

Итак, мы создали 5 файлов анимации.

И теперь можем применять их к View-компонентам.

Открываем main.xml и создадим экран:

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

Анимация читается из xml-файла методом AnimationUtils.loadAnimation, на выходе получается объект типа Animation. Его используем в методе startAnimation, который запускает анимацию.

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

Я использовал не все возможности и параметры. Есть еще, например, параметр android:startOffset – он указывает задержку при старте анимации. Т.е. если указать android:startOffset=”1000”, то анимация начнется через секунду. Это удобно использовать если вы делаете набор трансформ ( ) и вам надо чтобы они запускались не одновременно, а в определенном порядке. Также полезный параметр android:repeatCount – это количество повторов.

Рекомендую поиграть параметрами в XML файлах и посмотреть, что получается.

На следующем уроке:

— создаем в приложении второй экран

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Создание анимаций для Android с помощью MotionLayout

Russian (Pусский) translation by Ellen Nelson (you can also view the original English article)

Благодаря своей замечательной универсальности виджет ConstraintLayout стал «швейцарским ножом» для Android разработчиков при создании макетов. Добавление сложной анимации с ним возможно, однако это может быть довольно затратным по времени. Вот почему, на I/O 2018 Google выкатила виджет MotionLayout .

Виджет MotionLayout , который теперь является частью библиотеки поддержки Android, дополняет виджет ConstraintLayout . Это уникальный виджет, с которым вы можете анимировать его содержимое, используя только XML. Кроме того, он даёт вам детальный контроль над всей его анимацией.

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

Что необходимо

Для работы с этим руководством вам потребуется:

  • Android Studio 3.1.3 или выше
  • устройство или эмулятор с Android API уровня 21 или выше
  • общее представление о виджете ConstraintLayout

1. Добавление зависимостей

Чтобы использовать виджет MotionLayout в проекте Android Studio, в качестве зависимости implementation , у вас должна быть последняя версия библиотеки Constraint Layout. А чтобы избежать конфликта версий, убедитесь, что включили зависимость для последней стабильной 7 версии библиотеки appcompat.

Соответственно, добавьте следующий код в файл build.gradle модуля app :

2. Задаём макет

Виджет MotionLayout может делать всё, на что способен виджет ConstraintLayout . Таким образом вы можете спокойно заменить один на другой. Однако сейчас, я советую создать новый XMLфайл макета и добавить в него виджет MotionLayout в качестве корневого элемента.

В этом уроке мы будем анимировать виджет ImageView . Так, что добавьте его в качестве первого дочернего элемента.

Для виджета ImageView можете использовать любую графику drawable. В коде выше, я использую drawable с цветом.

Дальше добавляем кнопку, нажав на которую, запустится анимация. Следующий код показывает, как расположить её по центру макета:

Кроме того, для отслеживания хода выполнения анимации, добавьте в макет виджет SeekBar и поместите его под кнопкой. Вот как:

И, наконец, поскольку с кнопкой связан обработчик события «on-click», убедитесь, что вы указали это в вашей activity.

3. Создание сцены движения

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

Для создания сцены, создаём XML файл ресурсов и добавляем в него элемент MotionScene .

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

Читайте также:  Android версии по новинки

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

Обратите внимание, что каждый элемент ConstraintSet всегда должен указывать как желаемую позицию, так и желаемый размер. Это важно, потому что это изменит любую установленную ранее информацию макета.

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

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

Если название вашего файла сцены движения my_scene.xml, ваш виджет MotionLayout теперь должен выглядеть следующим образом:

4. Запуск анимации

Когда вы запускаете приложение, виджет MotionLayout автоматически применяет набор регуляторов, указанный в атрибуте constraintSetStart элемента Transition . Таким образом, для запуска анимации, всё, что нужно сделать, это вызвать метод transitionToEnd() виджета. В следующем коде, который необходимо добавить в обработчик событий по-клику, созданный на предыдущем шаге, показано, как это сделать:

На этом этапе, если вы запустите приложение и нажмёте кнопку, вы сможете увидеть, как виджет ImageView плавно перемещается по экрану.

5. Обработка событий анимации

Прикрепив объект TransitionListener к виджету MotionLayout , вы можете тщательно отслеживать ход анимации.

Интерфейс TransitionListener имеет два абстрактных метода, и Android Studio автоматически сгенерирует для них заглушки.

Метод onTransitionCompleted() вызывается, когда завершается переход от одного набора регуляторов к другому. А пока давайте используем его для сброса ограничений виджета ImageView , вызвав внутри него метод transitionToStart() .

Метод onTransitionChange() вызывается каждый раз, когда изменяется ход анимации. Так что, ход выполнения является числом с плавающей запятой, которое лежит между нулём и единицей. Следующий код показывает, как обновить SeekBar в зависимости по ходу анимации:

А теперь давайте снова запустим приложение, чтобы увидеть две анимации.

6. Создание ключевых кадров

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

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

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

Кадры KeyPosition помогают вам изменить форму траектории. При их создании убедитесь, что вы указали идентификатор целевого виджета, позицию на временной шкале, которая может быть любым числом от 0 до 100, и желаемые координаты X или Y, заданные в процентах. Координаты могут быть либо относительно фактических осей X или Y, либо относительно самой траектории.

В следующем коде показано, как создать два ключевых кадра, благодаря которым, виджет ImageView следует по нашему пути, избегая столкновений с кнопкой и строкой поиска:

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

Вы, конечно, можете добавить больше ключевых кадров. Например, добавив следующий ключевой кадр к концу временной шкалы, вы можете заставить виджет ImageView следовать по более волнистой траектории:

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

Следующий код создаёт кадр KeyCycle , который использует синусоидальную траекторию для поворота виджета ImageView на 50 градусов, время от времени:

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

7. Сделаем анимированный виджет интерактивным

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

Например, вы можете добавить следующий элемент OnClick , который предназначен для виджета ImageView , внутри элемента Transition вашей сцены движения и избавиться от кнопки:

Точно так же, вы можете использовать элемент OnSwipe , чтобы позволить пользователю вручную перетаскивать виджет ImageView по экрану. При создании элемента вы должны убедиться, что указали правильное направление перетаскивания и сторону виджета, которая должна выступать в качестве ручки для перемещения.

Если вы снова запустите приложение, вы сможете перетащить виджет ImageView .

Заключение

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

Стоит отметить, что в будущих версиях Android Studio будет визуальный редактор движения, который, вероятно, ещё больше улучшит удобство использования виджета.

Узнать больше можете из этой официальной документации.

Источник

Анимация в Android: плавные переходы фрагментов внутри Bottom Sheet

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

Данная статья о проблеме и анализе вариантов её решения. Я не дам вам серебряную пулю против всех монстров, но покажу, как можно изучить конкретного, чтобы создать пулю специально для него. Разберу это на примере того, как мы подружили анимацию смены фрагментов с Bottom Sheet.

Бриллиантовый чекаут: предыстория

Бриллиантовый чекаут — кодовое название нашего проекта. Смысл его очень прост — сократить время, затрачиваемое клиентом на последнем этапе оформления заказа. Если в старой версии для оформления заказа требовалось минимум четыре клика на двух экранах (а каждый новый экран — это потенциальная потеря контекста пользователем), «бриллиантовый чекаут» в идеальном случае требует всего один клик на одном экране.


Сравнение старого и нового чекаута

Между собой мы называем новый экран «шторка». На рисунке вы видите, в каком виде мы получили задание от дизайнеров. Данное дизайнерское решение является стандартным, известно оно под именем Bottom Sheet, описано в Material Design (в том числе для Android) и в разных вариациях используется во многих приложениях. Google предлагает нам два готовых варианта реализации: модальный (Modal) и постоянный (Persistent). Разница между этими подходами описана во многих и многих статьях.

Читайте также:  Где найти прошивку для android

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

Смотри, какая классная анимация на iOS. Давай так же сделаем?

Такой вызов не принять мы не могли! Ладно, шучу по поводу «дизайнеры неожиданно пришли с предложением сделать анимацию», но часть про iOS — чистая правда.

Стандартные переходы между экранами (то есть, отсутствие переходов) выглядели хоть и не слишком коряво, но до соответствия званию «бриллиантовый чекаут» не дотягивали. Хотя, кого я обманываю, это действительно было ужасно:

Что имеем «из коробки»

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

  1. Клиент нажимал на поле адреса пиццерии -> в ответ открывался фрагмент «Самовывоз». Открывался он на весь экран (так было задумано) с резким скачком, при этом список пиццерий появлялся с небольшой задержкой.
  2. Когда клиент нажимал «Назад» -> возврат на предыдущий экран происходил с резким скачком.
  3. При нажатии на поле способа оплаты -> снизу с резким скачком открывался фрагмент «Способ оплаты». Список способов оплаты появлялся с задержкой, при их появлении экран увеличивался со скачком.
  4. При нажатии «Назад» -> возврат обратно с резким скачком.

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

В чём, собственно, проблема: где клиенту хорошо, там у нас ограничения

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

Это привело нас к техническому ограничению: мы решили, что нам нельзя на каждую смену экрана закрывать текущий bottom sheet и показывать новый, а также будет плохо показывать несколько bottom sheet один над другим. Так, в рамках нашей реализации (каждый экран — новый фрагмент), можно сделать только один bottom sheet, который должен двигаться максимально плавно в ответ на действия пользователей.

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

Предварительная разметка

Корневой элемент «шторки» очень простой — это всего лишь прямоугольный фон с закруглёнными сверху углами и контейнер, в который помещаются фрагменты.

И файл dialog_gray200_background.xml выглядит так:

Каждый новый экран представляет собой отдельный фрагмент, фрагменты сменяются с помощью метода replace, тут всё стандартно.

Первые попытки реализовать анимацию

animateLayoutChanges

Вспоминаем о древней эльфийской магии animateLayoutChanges, которая на самом деле представляет собой дефолтный LayoutTransition. Хотя animateLayoutChanges совершенно не рассчитан на смену фрагментов, есть надежда, что это поможет с анимацией высоты. Также FragmentContainerView не поддерживает animateLayoutChanges, поэтому меняем его на старый добрый FrameLayout.

Как видим, изменение высоты контейнера действительно анимируется при смене фрагментов. Переход на экран «Самовывоз» выглядит нормально, но остальное оставляет желать лучшего.

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

setCustomAnimations

FragmentTransaction позволяет задать анимацию, описанную в xml-формате с помощью метода setCustomAnimation. Для этого в ресурсах создаём папку с названием «anim» и складываем туда четыре файла анимации:

И затем устанавливаем эти анимации в транзакцию:

Получаем вот такой результат:

Что мы имеем при такой реализации:

  • Уже стало лучше — видно как экраны сменяют друг друга в ответ на действие пользователя.
  • Но всё равно есть скачок из-за разной высоты фрагментов. Так происходит из-за того, что при переходе фрагментов в иерархии есть только один фрагмент. Именно он подстраивает высоту контейнера под себя, а второй отображается «как получилось».
  • Всё ещё есть проблема с асинхронной загрузкой данных о способах оплаты — экран появляется сначала пустым, а потом со скачком наполняется контентом.

Это никуда не годится. Вывод: нужно что-то другое.

А может попробуем что-то внезапное: Shared Element Transition

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

Суть его довольно проста — мы можем анимировать переход элементов одного фрагмента в другой. Например, можем элемент на первом фрагменте (назовём его «начальным элементом») с анимацией переместить на место элемента на втором фрагменте (этот элемент назовём «конечным элементом»), при этом с фэйдом скрыть остальные элементы первого фрагмента и с фэйдом показать второй фрагмент. Элемент, который должен анимироваться с одного фрагмента на другой, называется Shared Element.

Чтобы задать Shared Element, нам нужно:

  • пометить начальный элемент и конечный элемент атрибутом transitionName с одинаковым значением;
  • указать sharedElementEnterTransition для второго фрагмента.

А что, если использовать корневую View фрагмента в качестве Shared Element? Возможно Shared Element Transition придумывали не для этого. Хотя если подумать, сложно найти аргумент, почему это решение не подойдёт. Мы хотим анимировать начальный элемент в конечный элемент между двумя фрагментами. Не вижу идеологического противоречия. Давайте попробуем сделать так!

Для каждого фрагмента, который находится внутри «шторки», для корневой View указываем атрибут transitionName с одинаковым значением:

Важно: это будет работать, поскольку мы используем REPLACE в транзакции фрагментов. Если вы используете ADD (или используете ADD и скрываете предыдущий фрагмент с помощью previousFragment.hide() [не надо так делать]), то transitionName придётся задавать динамически и очищать после завершения анимации. Так приходится делать, потому что в один момент времени в текущей иерархии View не может быть две View с одинаковым transitionName. Осуществить это можно, но будет лучше, если вы сможете обойтись без такого хака. Если вам всё-таки очень нужно использовать ADD, вдохновение для реализации можно найти в этой статье.

Далее нужно указать класс Transition’а, который будет отвечать за то, как будет протекать наш переход. Для начала проверим, что есть «из коробки» — используем AutoTransition.

И мы должны задать Shared Element, который хотим анимировать, в транзакции фрагментов. В нашем случае это будет корневая View фрагмента:

Важно: обратите внимание, что transitionName (как и весь Transition API) доступен начиная с версии Android Lollipop.

Посмотрим, что получилось:

Транзишн сработал, но выглядит так себе. Так происходит, потому что во время транзакции фрагментов в иерархии View находится только новый фрагмент. Этот фрагмент растягивает или сжимает контейнер под свой размер и только после этого начинает анимироваться с помощью транзишна. Именно по этой причине мы видим анимацию только когда новый фрагмент больше по высоте, чем предыдущий.

Читайте также:  Чем открыть webp android

Раз стандартная реализация нам не подошла, что нужно сделать? Конечно же, нужно переписать всё на Flutter написать свой Transition!

Пишем свой Transition

Transition — это класс из Transition API, который отвечает за создание анимации между двумя сценами (Scene). Основные элементы этого API:

  • Scene — это расположение элементов на экране в определённый момент времени (layout) и ViewGroup, в которой происходит анимация (sceneRoot).
  • Начальная сцена (Start Scene) — это Scene в начальный момент времени.
  • Конечная сцена (End Scene) — это Scene в конечный момент времени.
  • Transition — класс, который собирает свойства начальной и конечной сцены и создаёт аниматор для анимации между ними.

В классе Transition мы будем использовать четыре метода:

  • fun getTransitionProperties(): Array. Данный метод должен вернуть набор свойств, которые будут анимироваться. Из этого метода нужно вернуть массив строк (ключей) в свободном виде, главное, чтобы методы captureStartValues и captureEndValues (описанные далее) записали свойства с этими ключами. Пример будет далее.
  • fun captureStartValues(transitionValues: TransitionValues). В данном методе мы получаем нужные свойства layout’а начальной сцены. Например, мы можем получить начальное расположение элементов, высоту, прозрачность и так далее.
  • fun captureEndValues(transitionValues: TransitionValues). Такой же метод, только для получения свойств layout’а конечной сцены.
  • fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator?. Этот метод должен использовать свойства начальной и конечной сцены, собранные ранее, чтобы создать анимацию между этими свойствами. Обратите внимание, что если свойства между начальной и конечной сценой не поменялись, то данный метод не вызовется вовсе.

Реализуем свой Transition за девять шагов

    Создаём класс, который представляет Transition.

Напоминаю, что Transition API доступен с версии Android Lollipop.
Реализуем getTransitionProperties.

Поскольку мы хотим анимировать высоту View, заведём константу PROP_HEIGHT, соответствующую этому свойству (значение может быть любым) и вернём массив с этой константой:

Нам нужно запомнить высоту той View, которая хранится в параметре transitionValues. Значение высоты нам нужно записать в поле transitionValues.values (он имеет тип Map) c ключом PROP_HEIGHT:

Всё просто, но есть нюанс. Вспомните, что во всех случаях ранее высота контейнера резко менялась в соответствии с высотой нового фрагмента. Чтобы такого не происходило, придётся что-то придумать. Проще всего просто «прибить гвоздями» контейнер фрагментов, то есть просто задать ему константную высоту, равную текущей высоте. При этом на экране ничего не произойдёт, но при смене фрагмента высота останется той же. В итоге метод будет выглядеть следующим образом:

Аналогично предыдущему методу, нужно запомнить высоту View. Но не всё так просто. На предыдущем шаге мы зафиксировали высоту контейнера. Новый фрагмент по высоте может быть меньше, равным или больше предыдущего фрагмента. В первых двух случаях мы можем просто взять высоту нового фрагмента. Однако в случае, когда новый фрагмент должен занять больше места, чем старый, значение высоты будет ограничено высотой контейнера. Поэтому придётся пойти на небольшую хитрость — мы просто измерим view, чтобы определить, сколько места на самом деле ей требуется. Реализация будет выглядеть так:

И метод getViewHeight:

Таким образом, мы знаем начальную и конечную высоту контейнера, и теперь дело за малым — создать анимацию.
Реализация анимации. Fade in.

Начальный фрагмент нам анимировать не нужно, так как при старте транзакции он удалится из иерархии. Будем показывать конечный фрагмент с фэйдом. Добавляем метод в класс «BottomSheetSharedTransition», ничего хитрого:

Реализация анимации. Анимация высоты.

Ранее мы запомнили начальную и конечную высоту, теперь мы можем анимировать высоту контейнера фрагментов:

Создаём ValueAnimator и обновляем высоту конечного фрагмента. Снова ничего сложного, но есть нюанс. Поскольку мы меняем высоту контейнера, после анимации его высота будет фиксированной. Это означает, что если фрагмент в ходе своей работы будет менять высоту, то контейнер не будет подстраиваться под это изменение. Чтобы этого избежать, по окончании анимации нужно установить высоту контейнера в значение WRAP_CONTENT. Таким образом, метод для анимации высоты контейнера будет выглядеть так:

Теперь всего лишь нужно использовать аниматоры, созданные этими функциями.
Реализация анимации. createAnimator.

Всегда анимируем переход.

Последний нюанс касательно реализации данного Transititon’а. Звёзды могут сойтись таким образом, что высота начального фрагмента будет точно равна высоте конечного фрагмента. Такое вполне может быть, если оба фрагмента занимают всю высоту экрана. В таком случае метод «createAnimator» не будет вызван совсем. Что же произойдёт?

  • Не будет Fade’а нового фрагмента, он просто резко появится на экране.
  • Поскольку в методе «captureStartValues» мы зафиксировали высоту контейнера, а анимации не произойдёт, высота контейнера никогда не станет равной WRAP_CONTENT.

Неприятно, но не смертельно. Можно обойти это поведение простым трюком: нужно добавить любое значение, которое будет отличаться для начальной сцены и конечной сцены, в список свойств Transition’а. Можно просто добавить строки с разными значениями:

Обратите внимание, добавилось свойство «PROP_VIEW_TYPE», и в методах «captureStartValues» и «captureEndValues» записываем разные значения этого свойства. Всё, транзишн готов!
Применяем Transition.

Асинхронная загрузка данных

Чтобы анимация началась вовремя и выглядела хорошо, нужно просто отложить переход между фрагментами (и, соответственно, анимацию) до момента, пока данные не будут загружены. Для этого внутри фрагмента нужно вызвать метод postponeEnterTransition. По окончании долгих задач по загрузке данных не забудьте вызвать startPostponedEnterTransition. Я уверен, вы знали об этом приёме, но напомнить лишний раз не помешает.

Всё вместе: что в итоге получилось

С новым BottomSheetSharedTransition и использованием postponeEnterTransition при асинхронной загрузке данных у нас получилась такая анимация:

Когда у нас есть готовый класс Transition’а, его применение сводится к простым шагам:

Шаг 1. При транзакции фрагмента добавляем Shared Element и устанавливаем Transition:

Шаг 2. В разметке фрагментов (текущего фрагмента и следующего), которые должны анимироваться внутри BottomSheetDialogFragment, устанавливаем transitionName:

На этом всё, конец.

А можно было сделать всё иначе?

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

  • Отказаться от фрагментов, использовать один фрагмент с множеством View и анимировать конкретные View. Так вы получите больший контроль над анимацией, но потеряете преимущества фрагментов: нативную поддержку навигации и готовую обработку жизненного цикла (придётся реализовывать это самостоятельно).
  • Использовать MotionLayout. Технология MotionLayout на данный момент всё ещё находится на стадии бета, но выглядит очень многообещающе, и уже есть официальные примеры, демонстрирующие красивые переходы между фрагментами.
  • Не использовать анимацию. Да, наш дизайн является частным случаем, и вы вполне можете счесть анимацию в данном случае избыточной. Вместо этого можно показывать один Bottom Sheet поверх другого или скрывать один Bottom Sheet и следом показывать другой.
  • Отказаться от Bottom Sheet совсем. Нет изменения высоты контейнера фрагментов — нет проблем.

Демо проект можно найти вот тут на GitHub. А вакансию Android-разработчика (Нижний Новгород) вот здесь на Хабр Карьера.

Источник

Оцените статью