Фреймворк переходов Android позволяет анимировать все виды движения в пользовательском интерфейсе, предоставляя начальный и конечный макеты. Вы можете выбрать тип анимации, например появление или исчезновение представлений или изменение их размеров, а фреймворк переходов определит, как анимировать переход от начального макета к конечному.
В рамках перехода доступны следующие функции:
- Анимация на уровне группы применяется ко всем представлениям в иерархии.
- Встроенные анимации. Используйте готовые анимации для распространенных эффектов, таких как затухание или движение.
- Поддержка файлов ресурсов. Загружайте иерархии View и встроенные анимации из файлов ресурсов разметки.
- Обратные вызовы жизненного цикла. Получайте обратные вызовы, которые позволяют управлять анимацией и процессом изменения иерархии.
Базовый процесс анимации между двумя макетами выглядит следующим образом:
- Создайте объект
Sceneдля начального и конечного макетов. Однако сцена начального макета часто определяется автоматически на основе текущего макета. - Создайте объект
Transition, чтобы определить тип анимации. - Вызовите метод
TransitionManager.go(), и система запустит анимацию для замены макетов.
На диаграмме на рисунке 1 показана связь между макетами, сценами, переходом и итоговой анимацией.
Рисунок 1. Базовая иллюстрация того, как фреймворк переходов создает анимацию.
Создать сцену
Сцены хранят состояние иерархии View, включая все ее представления и значения их свойств. Фреймворк переходов может запускать анимацию между начальной и конечной сценами.
Вы можете создавать сцены из файла ресурса макета или из группы представлений в коде. Однако начальная сцена перехода часто определяется автоматически на основе текущего интерфейса.
Для сцены также можно задать действия, которые будут выполняться при ее изменении. Эта функция полезна для очистки настроек просмотра после перехода к сцене.
Как создать сцену на основе ресурса разметки
Вы можете создать экземпляр Scene непосредственно из файла ресурса макета. Используйте этот метод, когда иерархия View в файле в основном статична.
Полученная сцена представляет состояние иерархии View на момент создания экземпляра Scene. Если вы измените иерархию View, воссоздайте сцену. Фреймворк создает сцену на основе всей иерархии View в файле. Нельзя создать сцену из части файла макета.
Чтобы создать экземпляр Scene из файла ресурса макета, извлеките корневой элемент сцены из макета в виде ViewGroup. Затем вызовите функцию Scene.getSceneForLayout() с корневым элементом сцены и идентификатором ресурса файла макета, который содержит иерархию View для сцены.
Как задать макеты для сцен
Фрагменты кода в оставшейся части этого раздела показывают, как создать две разные сцены с одним и тем же корневым элементом. Фрагменты кода также показывают, что вы можете загрузить несколько несвязанных объектов Scene, не подразумевая, что они связаны друг с другом.
Пример состоит из следующих определений макета:
- Основной макет действия с текстовой меткой и дочерним элементом
FrameLayout. ConstraintLayoutдля первой сцены с двумя текстовыми полями.ConstraintLayoutдля второй сцены с теми же двумя текстовыми полями в другом порядке.
В примере вся анимация происходит в дочернем макете основного макета действия. Текстовая метка в основном макете остается статической.
Основной макет для объекта activity определяется следующим образом:
res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/master_layout"> <TextView android:id="@+id/title" ... android:text="Title"/> <FrameLayout android:id="@+id/scene_root"> <include layout="@layout/a_scene" /> </FrameLayout> </LinearLayout>
Это определение макета содержит текстовое поле и дочерний элемент FrameLayout для корневого элемента сцены. Макет для первой сцены включен в основной файл макета.
Это позволяет приложению отображать его как часть начального пользовательского интерфейса, а также загружать его в сцену, поскольку фреймворк может загружать в сцену только весь файл макета.
Макет для первой сцены определяется следующим образом:
res/layout/a_scene.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text_view1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 1" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> <TextView android:id="@+id/text_view2" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 2" app:layout_constraintTop_toBottomOf="@id/text_view1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Макет второй сцены содержит те же два текстовых поля с теми же идентификаторами, но в другом порядке. Определяется следующим образом:
res/layout/another_scene.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text_view2" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 2" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <TextView android:id="@+id/text_view1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Text Line 1" app:layout_constraintTop_toBottomOf="@id/text_view2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
Как создавать сцены на основе макетов
После того как вы создадите определения для двух макетов ограничений, вы можете получить сцену для каждого из них. Это позволяет переключаться между двумя конфигурациями интерфейса. Чтобы получить сцену, вам нужна ссылка на корневой элемент сцены и идентификатор ресурса разметки.
В следующем фрагменте кода показано, как получить ссылку на корень сцены и создать два объекта Scene из файлов макета:
Kotlin
val sceneRoot: ViewGroup = findViewById(R.id.scene_root) val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this) val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)
Java
Scene aScene; Scene anotherScene; // Create the scene root for the scenes in this app. sceneRoot = (ViewGroup) findViewById(R.id.scene_root); // Create the scenes. aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this); anotherScene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);
Теперь в приложении есть два объекта Scene, основанных на иерархиях View. В обеих сценах используется корневой элемент, определенный элементом FrameLayout в файле res/layout/activity_main.xml.
Создайте сцену в коде
Вы также можете создать экземпляр Scene в коде из объекта ViewGroup. Используйте этот метод, когда вы изменяете иерархии View непосредственно в коде или когда вы генерируете их динамически.
Чтобы создать сцену из иерархии View в коде, используйте конструктор Scene(sceneRoot, viewHierarchy). Вызов этого конструктора эквивалентен вызову функции Scene.getSceneForLayout(), когда вы уже заполнили файл макета.
В следующем фрагменте кода показано, как создать экземпляр Scene из корневого элемента сцены и иерархии View для сцены в коде:
Kotlin
val sceneRoot = someLayoutElement as ViewGroup val viewHierarchy = someOtherLayoutElement as ViewGroup val scene: Scene = Scene(sceneRoot, viewHierarchy)
Java
Scene mScene; // Obtain the scene root element. sceneRoot = (ViewGroup) someLayoutElement; // Obtain the view hierarchy to add as a child of // the scene root when this scene is entered. viewHierarchy = (ViewGroup) someOtherLayoutElement; // Create a scene. mScene = new Scene(sceneRoot, mViewHierarchy);
Создание действий для сцены
Фреймворк позволяет определять собственные действия, которые система выполняет при входе в сцену или выходе из нее. Во многих случаях определять пользовательские действия сцены не нужно, поскольку фреймворк автоматически анимирует изменения между сценами.
Действия сцены полезны в следующих случаях:
- Для анимации представлений, которые не находятся в одной иерархии. Вы можете анимировать представления для начальной и конечной сцен, используя действия для сцены входа и выхода.
- Для анимации представлений, которые фреймворк переходов не может анимировать автоматически, например объектов
ListView. Подробнее об этом рассказывается в разделе Ограничения.
Чтобы добавить собственные действия, определите их как объекты Runnable и передайте в функции Scene.setExitAction() или Scene.setEnterAction(). Фреймворк вызывает функцию setExitAction() в начальной сцене перед запуском анимации перехода и функцию setEnterAction() в конечной сцене после запуска анимации перехода.
Как добавить переход
Фреймворк перехода представляет стиль анимации между сценами с объектом Transition. Вы можете создать экземпляр Transition, используя встроенные подклассы, такие как AutoTransition и Fade, или определить собственный переход.
Затем вы можете запустить анимацию между сценами, передав конечную Scene и Transition в TransitionManager.go().
Жизненный цикл перехода похож на жизненный цикл объекта activity и представляет собой состояния перехода, которые фреймворк отслеживает между началом и завершением анимации. На важных этапах жизненного цикла фреймворк вызывает функции обратного вызова, которые можно реализовать для настройки пользовательского интерфейса на разных этапах перехода.
Как создать переход
В предыдущем разделе показано, как создавать сцены, представляющие состояние различных иерархий представлений. Определив начальную и конечную сцены, между которыми нужно выполнить переход, создайте объект Transition, который определяет анимацию.
Фреймворк позволяет либо указать встроенный переход в файле ресурсов и раздуть его в коде, либо создать экземпляр встроенного перехода непосредственно в коде.
Таблица 1. Встроенные типы переходов.
| Класс | Тег | Эффект |
|---|---|---|
AutoTransition |
<autoTransition/> |
Переход по умолчанию. Затемняет, перемещает, изменяет размер и осветляет представления в указанном порядке. |
ChangeBounds |
<changeBounds/> |
Перемещает и изменяет размер представлений. |
ChangeClipBounds |
<changeClipBounds/> |
Захватывает View.getClipBounds() до и после смены сцены и анимирует эти изменения во время перехода. |
ChangeImageTransform |
<changeImageTransform/> |
Захватывает матрицу ImageView до и после смены сцены и анимирует ее во время перехода. |
ChangeScroll |
<changeScroll/> |
Фиксирует свойства прокрутки целевых объектов до и после изменения сцены и анимирует любые изменения. |
ChangeTransform |
<changeTransform/> |
Фиксирует масштаб и поворот представлений до и после изменения сцены и анимирует эти изменения во время перехода. |
Explode |
<explode/> |
Отслеживает изменения видимости целевых представлений в начальной и конечной сценах и перемещает представления за пределы сцены или внутрь нее. |
Fade |
<fade/> |
fade_in – затухание в представлениях.fade_out – затухание.fade_in_out (по умолчанию) выполняет fade_out, а затем fade_in.
|
Slide |
<slide/> |
Отслеживает изменения видимости целевых представлений в начальной и конечной сценах и перемещает представления внутрь или наружу от одного из краев сцены. |
Создание экземпляра перехода из файла ресурсов
Этот метод позволяет изменять определение перехода, не меняя код действия. Этот метод также полезен для отделения сложных определений переходов от кода приложения, как показано в разделе Указание нескольких переходов.
Чтобы указать встроенный переход в файле ресурсов, выполните следующие действия:
- Добавьте каталог
res/transition/в проект. - Создайте в этом каталоге новый XML-файл ресурсов.
- Добавьте узел XML для одного из встроенных переходов.
Например, в следующем файле ресурсов указан переход Fade:
res/transition/fade_transition.xml
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
В следующем фрагменте кода показано, как создать экземпляр Transition в объекте activity из файла ресурсов:
Kotlin
var fadeTransition: Transition = TransitionInflater.from(this) .inflateTransition(R.transition.fade_transition)
Java
Transition fadeTransition = TransitionInflater.from(this). inflateTransition(R.transition.fade_transition);
Создайте экземпляр перехода в коде
Этот метод полезен для динамического создания объектов перехода, если вы изменяете пользовательский интерфейс в своем коде, а также для создания простых встроенных экземпляров перехода с небольшим количеством параметров или без них.
Чтобы создать экземпляр встроенного перехода, вызовите один из общедоступных конструкторов в подклассах класса Transition. Например, следующий фрагмент кода создает экземпляр перехода Fade:
Kotlin
var fadeTransition: Transition = Fade()
Java
Transition fadeTransition = new Fade();
Как добавить переход
Обычно переход применяется для изменения между различными иерархиями View в ответ на событие, например действие пользователя. Например, в поисковом приложении, когда пользователь вводит поисковый запрос и нажимает кнопку поиска, приложение переходит к сцене, представляющей макет результатов, применяя переход, который скрывает кнопку поиска и показывает результаты поиска.
Чтобы изменить сцену, применив переход в ответ на событие в действии, вызовите функцию класса TransitionManager.go() с конечной сценой и экземпляром перехода, который будет использоваться для анимации, как показано в следующем фрагменте:
Kotlin
TransitionManager.go(endingScene, fadeTransition)
Java
TransitionManager.go(endingScene, fadeTransition);
Фреймворк изменяет иерархию View внутри корневого элемента сцены на иерархию View из конечной сцены, запуская анимацию, указанную экземпляром перехода. Начальная сцена – это конечная сцена из последнего перехода. Если предыдущего перехода нет, начальная сцена определяется автоматически на основе текущего состояния пользовательского интерфейса.
Если вы не укажете экземпляр перехода, менеджер переходов может применить автоматический переход, который делает что-то разумное в большинстве ситуаций. Дополнительную информацию можно найти в документации по API для класса TransitionManager.
Выбрать целевые просмотры
По умолчанию фреймворк применяет переходы ко всем представлениям в начальной и конечной сценах. В некоторых случаях может потребоваться применить анимацию только к подмножеству представлений в сцене. Фреймворк позволяет выбирать конкретные представления, которые вы хотите анимировать. Например, фреймворк не поддерживает анимацию изменений объектов ListView, поэтому не пытайтесь анимировать их во время перехода.
Каждое представление, которое анимирует переход, называется целью. Вы можете выбирать только цели, которые являются частью иерархии View, связанной со сценой.
Чтобы удалить одно или несколько представлений из списка целей, вызовите метод removeTarget() перед началом перехода. Чтобы добавить в список целей только определенные представления, вызовите функцию addTarget(). Дополнительную информацию можно найти в документации по API для класса Transition.
Как указать несколько переходов
Чтобы анимация была максимально эффективной, подберите ее в соответствии с типом изменений, происходящих между сценами. Например, если вы удаляете некоторые представления и добавляете другие между сценами, анимация затухания или появления обеспечивает заметную индикацию того, что некоторые представления больше недоступны. Если вы перемещаете представления в разные точки экрана, лучше анимировать движение, чтобы пользователи заметили новое местоположение представлений.
Вам не нужно выбирать только одну анимацию, поскольку фреймворк переходов позволяет комбинировать эффекты анимации в наборе переходов, который содержит группу отдельных встроенных или настраиваемых переходов.
Чтобы определить набор переходов из коллекции переходов в XML, создайте файл ресурсов в каталоге res/transitions/ и перечислите переходы в элементе TransitionSet. Например, в следующем фрагменте кода показано, как указать набор переходов, который ведет себя так же, как класс AutoTransition:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="sequential"> <fade android:fadingMode="fade_out" /> <changeBounds /> <fade android:fadingMode="fade_in" /> </transitionSet>
Чтобы преобразовать набор переходов в объект TransitionSet в коде, вызовите функцию TransitionInflater.from() в объекте activity. Класс TransitionSet расширяет класс Transition, поэтому его можно использовать с менеджером переходов так же, как и любой другой экземпляр Transition.
Как применить переход без сцен
Изменение иерархии View – не единственный способ изменить пользовательский интерфейс. Вы также можете добавлять, изменять и удалять дочерние представления в текущей иерархии.
Например, вы можете реализовать взаимодействие с поиском с помощью одного макета. Начните с макета, показывающего поле ввода поиска и значок поиска. Чтобы изменить пользовательский интерфейс для показа результатов, удалите кнопку поиска, когда пользователь нажимает на нее, вызвав функцию ViewGroup.removeView(), и добавьте результаты поиска, вызвав функцию ViewGroup.addView().
Этот подход можно использовать, если альтернативой является наличие двух почти идентичных иерархий. Вместо того чтобы создавать и поддерживать два отдельных файла макета для незначительного различия в пользовательском интерфейсе, вы можете использовать один файл макета, содержащий иерархию View, которую вы изменяете в коде.
Если вы вносите изменения в текущую иерархию View таким образом, вам не нужно создавать сцену. Вместо этого вы можете создать и применить переход между двумя состояниями иерархии View, используя отложенный переход. Эта функция фреймворка переходов начинает работу с текущего состояния иерархии View, записывает изменения, которые вы вносите в ее представления, и применяет переход, который анимирует изменения, когда система перерисовывает пользовательский интерфейс.
Чтобы создать отложенный переход в рамках одной иерархии View, выполните следующие действия:
- Когда происходит событие, запускающее переход, вызовите функцию
TransitionManager.beginDelayedTransition(), указав родительское представление для всех представлений, которые вы хотите изменить, и переход, который нужно использовать. Фреймворк сохраняет текущее состояние дочерних представлений и значения их свойств. - Внесите изменения в дочерние представления в соответствии с вашими потребностями. Фреймворк записывает изменения, которые вы вносите в дочерние представления и их свойства.
- Когда система перерисовывает пользовательский интерфейс в соответствии с вашими изменениями, фреймворк анимирует изменения между исходным состоянием и новым состоянием.
В следующем примере показано, как анимировать добавление текстового представления в иерархию представлений с помощью отложенного перехода. Первый фрагмент кода – это файл определения макета:
res/layout/activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/mainLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/inputText" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> ... </androidx.constraintlayout.widget.ConstraintLayout>
В следующем фрагменте показан код, который анимирует добавление текстового представления:
MainActivity
Kotlin
setContentView(R.layout.activity_main) val labelText = TextView(this).apply { text = "Label" id = R.id.text } val rootView: ViewGroup = findViewById(R.id.mainLayout) val mFade: Fade = Fade(Fade.IN) TransitionManager.beginDelayedTransition(rootView, mFade) rootView.addView(labelText)
Java
private TextView labelText; private Fade mFade; private ViewGroup rootView; ... // Load the layout. setContentView(R.layout.activity_main); ... // Create a new TextView and set some View properties. labelText = new TextView(this); labelText.setText("Label"); labelText.setId(R.id.text); // Get the root view and create a transition. rootView = (ViewGroup) findViewById(R.id.mainLayout); mFade = new Fade(Fade.IN); // Start recording changes to the view hierarchy. TransitionManager.beginDelayedTransition(rootView, mFade); // Add the new TextView to the view hierarchy. rootView.addView(labelText); // When the system redraws the screen to show this update, // the framework animates the addition as a fade in.
Определите обратные вызовы жизненного цикла перехода
Жизненный цикл перехода похож на жизненный цикл объекта activity. Они представляют собой переходные состояния, которые фреймворк отслеживает в период между вызовом функции TransitionManager.go() и завершением анимации. На важных этапах жизненного цикла фреймворк вызывает обратные вызовы, определенные интерфейсом TransitionListener.
Обратные вызовы жизненного цикла перехода полезны, например, для копирования значения свойства представления из иерархии начального представления в иерархию конечного представления во время изменения сцены. Вы не можете просто скопировать значение из начального представления в представление в конечной иерархии View, поскольку конечная иерархия View не наполняется до завершения перехода. Вместо этого вам нужно сохранить значение в переменной, а затем скопировать его в конечную иерархию View, когда фреймворк завершит переход. Чтобы получать уведомления о завершении перехода, реализуйте в действии функцию TransitionListener.onTransitionEnd().
Дополнительную информацию можно найти в документации по API для класса TransitionListener.
Ограничения
В этом разделе перечислены некоторые известные ограничения фреймворка переходов:
- Анимация, примененная к
SurfaceView, может отображаться некорректно. ЭкземплярыSurfaceViewобновляются не из потока UI, поэтому обновления могут быть не синхронизированы с анимацией других представлений. - Некоторые типы переходов могут не давать желаемого эффекта анимации при применении к
TextureView. - Классы, расширяющие
AdapterView, напримерListView, управляют дочерними представлениями способами, несовместимыми с фреймворком переходов. Если вы попытаетесь анимировать представление на основеAdapterView, экран устройства может перестать отвечать. - Если вы попытаетесь изменить размер
TextViewс анимацией, текст переместится в новое место до того, как размер объекта будет полностью изменен. Чтобы избежать этой проблемы, не анимируйте изменение размера представлений, содержащих текст.