Android fragment change animation

Анимация фрагментов

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

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

Разметка для активности:

Создадим разметки для фрагментов.

fragment1.xml

fragment2.xml

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

Фрагмент должен иметь свой класс. Для примера достаточно простейшего кода, где нужно указать разметку фрагмента.

Fragment1.java

По такому же принципу создайте класс для второго фрагмента самостоятельно.

Теперь перейдём к коду для главной активности:

В коде я закомментировал две строчки для анимации. В таком виде переключение между фрагментами происходит стандартным способом без анимации. Теперь раскомментируйте первую строчку transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);. В классе FragmentTransaction есть несколько готовых анимаций. С помощью метода setTransition(int transit) мы можем указать нужную анимацию и увидеть её в действии.

  • TRANSIT_FRAGMENT_CLOSE
  • TRANSIT_FRAGMENT_OPEN
  • TRANSIT_FRAGMENT_FADE
  • TRANSIT_NONE

Снова закомментируйте строку и раскомментируйте вторую строку transaction.setCustomAnimations(R.animator.slide_in_left, R.animator.slide_in_right);. Метод setCustomAnimations() позволяет указать собственную анимацию. Методу передаются два параметра. Первый параметр описывает анимацию для фрагмента, который появляется, а второй — описывает анимацию для фрагмента, который убирается с экрана устройства. Метод следует вызывать до появления фрагментов, иначе анимация не будет применена.

С методом setCustomAnimations() нужно быть осторожным при работе с фрагментами из support-библиотеки. В одной из версий библиотеки разработчики из Гугла всё поломали и код перестал работать. Неизвестно когда починят. Поэтому используйте стандартные фрагменты.

Для анимации нужно создать XML-файлы в папке res/animator (её тоже нужно создать вручную).

slide_in_left.xml

slide_in_right.xml

Элементы визуальных эффектов задаются в теге objectAnimator. У атрибута propertyName указывается свойство фрагмента, которое мы будем изменять при анимации, valueType указывает тип изменяемого параметра. Атрибуты valueFrom и valueTo указывают диапазон изменения параметра, указанного в propertyName. Если параметр valueFrom не указан, то значение берётся равное текущему. В нашем случае valueFrom равен -1280, это означает, что движение фрагмента по оси y будет начинаться со значения -1280 и перемещение будет происходить пока значение y не станет равным 0 для верхнего левого угла нашего фрагмента в течении 1500 миллисекунд (атрибут duration).

Тег set служит для объединения эффектов либо их разделения. В файле slide_in_right.xml используется атрибут ordering со значением together, что означает проигрывать эффекты одновременно, в противовес ему существует значение sequentially, которое требует последовательного отображения эффектов в анимации.

Переворачиваем карту

Напишем ещё один пример для закрепления материала.

Найдите в интернете две картинки, например, изображения карты и её рубашки. Подготовим четыре файла в папке res/animator.

card_flip_left_enter.xml

card_flip_left_exit.xml

card_flip_right_enter.xml

card_flip_right_exit.xml

В файле res/values/strings.xml добавьте пару новых ресурсов.

Теперь создадим два макета для двух фрагментов в папке res/layout. Фрагменты будут содержать по одной картинке.

fragment_card_front.xml

fragment_card_back.xml

Создаём два класса для фрагментов, которые загружают свои макеты.

Упростим макет активности.

Осталось написать код. Ещё раз напомню, что используйте стандартные фрагменты, а не из библиотеки совместимости.

Контейнер обрабатывает касание пальца как щелчок и переворачивает фрагмент на другой. Результат в видео.

Источник

Анимация фрагментов в Android

Всем доброго времени суток. Этот пост хочу посвятить теме фрагментов для Android. На Хабре есть уже переводы и некоторые статьи, которые упоминают о том, как начать работать с фрагментами под Android. Например, статья Fragments API в Android 3.0. В ней находится описание того, что такое фрагменты и в какой версии Android они доступны, поэтому те, кто ещё не добрался до неё могут при желании ознакомиться, я же не буду пересказывать этого в своём посте. Поэтому сразу перейду к делу.

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

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

Теперь перейдём к сути поста. Разработчиками Google фрагменты были наделены, на мой взгляд, отличной поддержкой анимации отображения самого фрагмента. Об этом и пойдёт далее речь. Я искал по Хабру посты на данную тематику, но так ничего и не нашёл, поэтому сейчас поделюсь своими знаниями.

Создаём проект

Давайте создадим небольшой проект. Я создал проект под свой Samsung Nexus S, там у меня стоит версия Android 4.1.2, собственно её я и использовал (Api Level 16). Сам проект я назвал FragmentsAnimationTest.

Для демонстрации нам понадобится главное активити и его лейаут, пара фрагментов, каждый также со своим лейаутом и ещё пара xml-файлов для самой анимации, о которых я расскажу позже.

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

Сначала расположим элементы главного активити в файле activity_main.xml:

Из кода видно, что используется главный лейаут — RelativeLayout, достаточно удобный при работе с фрагментами, в него помещаются два стандартных элемента FrameLayout — собственно, он и будет являться контейнером для фрагментов и кнопка, которой будем переключать фрагменты между собой. Пока что всё должно быть предельно просто.

Дальше займёмся нашими фрагментами. Создадим для них разметки и сами классы:
fragment1.xml

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

В классах также должно быть всё понятно, если знакомы с темой фрагментов. В них просто указывается какой именно леаут будет использоваться при отображении конкретного фрагмента и всё.

Читайте также:  Что лучше qualcomm или android что это

Теперь приступим к самому вкусному. Поработаем с классом главной активити, вот её код:

Разберём что именно происходит в нашей активити. Сначала создаются оба фрагмента, которые, как уже говорилось, будут поочерёдно менять друг друга. Далее указываем строкой ft = getFragmentManager().beginTransaction() получаем FragmentTransaction, с помощью которой мы сможем взаимодействовать с нашими фрагментами, но это всё есть в статье, которую я указывал ранее. Прежде, чем перейти к разбору следующего кода, сделаю небольшое отступление.

Существует две возможности создавать анимацию для отображения фрагментов:

  • 1ый способ — это подключение стандартной анимации с помощью метода setTransition(int transit). В классе FragmentTransaction есть несколько уже предописанных анимаций.
  • 2ой способ — это именно то, что нас интересует в данной теме, реализация кастомной анимации. Осуществляется с помощью метода setCustomAnimations()

Теперь вернёмся к разбору нашего кода. Посмотрим на следующую строку ft.setCustomAnimations(R.animator.slide_in_left, R.animator.slide_in_right), тут происходит подключение анимации ко всем фрагментам, с которыми будет происходить работа до завершения транзакции, до строки ft.commit(). Что же именно происходит? В setCustomAnimations передаётся два параметра. Первый параметр описывает анимацию, для отображения фрагмента, который появляется, а второй — описывает анимацию для фрагмента, который сменяется, т.е. убирается с экрана устройства. Важно упомянуть, что данный метод следует вызвать до появления фрагментов, в противном случае, анимация не будет применена.

Пара сток-комментариев — это для того, чтобы можно было попробовать поиграться с предописанной анимацией, достаточно их раскомментировать и закомментировать предыдущую строку — ft.setCustomAnimations(R.animator.slide_in_left, R.animator.slide_in_right), в обоих случаях, хотя это и необязательно.

Давайте разберём код активити до конца и перейдём к созданию самой анимации.

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

Создаём анимацию

Перейдём к главной части нашей темы. Научимся создавать саму анимацию. Способ создания анимации здесь немного отличается от того, как мы привыкли это делать в ранних версиях Android. Реализация проходит следующим образом. Для на чала нужно создать папку animator в папке ресурсов приложения, это будет выглядеть так — res/animator/. Сюда мы должны положить xml-файлы, которые будут описывать как именно должна проигрываться анимация. Поместим их туда:
slide_in_left.xml

Теперь подробно их разберём. Элементы самих визуальных эффектов описываются в теге objectAnimator, каждый такой тег говорит об описании нового эффекта анимации. Теперь посмотрим на сами атрибуты. Первый атрибут в файле slide_in_left.xml — это interpolator, у него имеется несколько значений, о них можно более подробно узнать из документации Property Animation. Interpolator отвечает за то, чтобы отобразить определённым способом в течении определённого времени наш фрагмент. Далее у нас следует атрибут propertyName, в нём указывается с какое именно свойство фрагмента мы будем изменять при анимации, в нашем примере первым идёт y, а valueType указывает какого типа у нас именяемый параметр. В книге Pro Android 4 аргументируется эта ситуация тем, что если посмотреть на метод setX() в классе View, то станет понятно, что он принимает значение типа float, с методом setY() дело обстоит также, отсюда и значение floatType.

Далее идут не маловажные атрибуты valueFrom и valueTo, они указывают от какого до какого значания изменять значение указаннное в propertyName, в нашем первом случае это y. Если параметр valueFrom не указан, то значение берётся равное текущему. В нашем случае valueFrom равен -1280, это означает, что движение фрагмента по оси y будет начинаться со значения -1280, это значение было выбрано из-за того, что оно находится за пределами экрана устройства и перемещение будет происходить пока значение y не станет равным 0 для верхнего левого угла нашего фрагмента в течении 1500 миллисекунд. И, наконец, duration — атрибут указывает сколько именно будет длиться наш анимированный эффект в миллисекундах.

И последний нюанс, который я хочу описать. Глядя в какой-либо из файлов описания анимации, можно заметить тэг set, в который помещены все эффекты анимации, он служит для объединения эффектов либо их разделения. В файле slide_in_right.xml используется атрибут ordering в теге set, в нашем случае он имеет значение together, что означает проигрывать эффекты одновременно, в противовес ему существует значение sequentially, которое требует последовательного отображения эффектов в анимации, что очень удобно в некоторых случаях.

Собственно и всё. В файле slide_in_right.xml приведен пример как можно использовать другие свойства для анимирования, например alpha-канал. Надеюсь данная статья пригодится тем, кому не всё равно как будет выглядеть его приложение.

Как вы сами понимаете, уважаемые хабраюзеры, скрины не смогут отобразить результат работы.

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

Источник

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

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

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

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

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


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

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

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

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

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

Читайте также:  Walkman xperia для андроид

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

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

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

  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 находится только новый фрагмент. Этот фрагмент растягивает или сжимает контейнер под свой размер и только после этого начинает анимироваться с помощью транзишна. Именно по этой причине мы видим анимацию только когда новый фрагмент больше по высоте, чем предыдущий.

Раз стандартная реализация нам не подошла, что нужно сделать? Конечно же, нужно переписать всё на 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-разработчика (Нижний Новгород) вот здесь на Хабр Карьера.

Источник

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