- PVSM.RU - https://www.pvsm.ru -
Всем привет!
На хабре ещё не была освещена тема Transitions API для анимаций, которые появились в Android начиная с 4.4 (KitKat) и продолжили свое развитие в 5.0 (Lollipop). В своей статье я расскажу о том, как упростить работу с анимациями с их использованием и как применять их на любом устройстве с версией Android 3.0 и выше.

Вместе с Android 4.4 был представлен новый механизм анимации изменений в layout. Ещё в версии 4.0 появился первый вариант решения этой проблемы — флаг animateLayoutChange для ViewGroup. Но даже с вызовом метода getLayoutTransition() и изменением его параметров он был недостаточно гибок и не давал полного контроля над тем, как будет анимироваться наше изменение (transition).
KitKat Transition API приносит нам понятия сцены — Scene и некоторого изменения между сценами — Transition. В добавок к ним вводим понятие Scene root для определения корневого layout, внутри которого будут происходить изменения сцен. А сама сцена это некоторый враппер над ViewGroup, который описывает конкретное состояние его самого и всех содержащихся в нем объектов View.
Теперь про сам Transition. Это механизм, отвечающий за считывание требуемых значений параметров у View, которые изменились при смене сцен и генерирующий анимации для плавного изменения этих состояний.
Начнем с самого простого варианта использования Transitions API.
Представим, что текущее состояние нашего layout это первая сцена. Допустим у нас просто изображен квадрат.
Опишем layout через xml
<FrameLayout
android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/transition_square"
android:layout_width="@dimen/square_size_normal"
android:layout_height="@dimen/square_size_normal"
android:background="@color/square_bg"/>
</FrameLayout>
Теперь мы просто хотим анимированно увеличить размер нашего квадрата. Чтобы уже начать привыкать к понятию сцен назовем это изменение переходом ко второй сцене.
ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.container);
View square = mSceneRoot.findViewById(R.id.transition_square);
int newSquareSize = getResources().getDimensionPixelSize(R.dimen.square_size_expanded);
// вызываем метод, говорящий о том, что мы хотим анимировать следующие изменения внутри sceneRoot
TransitionManager.beginDelayedTransition(sceneRoot);
// и применим сами изменения
ViewGroup.LayoutParams params = square.getLayoutParams();
params.width = newSquareSize;
params.height = newSquareSize;
square.setLayoutParams(params);
Результат:

Неплохо, анимация всего одной строкой. Причем таких изменений внутри layout может быть любое количество.
Теперь попробуем настроить некоторые параметры анимации. Для этого нужно указать конкретный Transition, который мы будем выполнять. Метод beginDelayedTransition может принимать вторым параметром любого наследника класса Transition. Именно про них мы сейчас и поговорим.
Вернемся к сценам. В первом примере мы разобрали самый простой способ использования API. Но сцены можно описывать и более формально, например, чтобы было удобнее между ними переключаться.
Опишем layout нашего Activity так, чтобы в нем была кнопка для смены сцен и FrameLayout, который будет нашим Scene root. Внутрь его сразу ставим layout, который является первой сценой, но вынесем его в отдельный файл.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btn_change_scene"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change scene"
android:layout_gravity="center_horizontal" />
<FrameLayout
android:id="@+id/scene_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/scene1"/>
</FrameLayout>
</LinearLayout>
Содержимое scene1. Поместим туда всё тот же квадрат, но в левый верхний угол
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/transition_square"
android:layout_width="@dimen/square_size_normal"
android:layout_height="@dimen/square_size_normal"
android:background="@color/square_bg"
android:layout_gravity="top|left"/>
</FrameLayout>
И создадим файл scene2. Квадрат переместим в правый верхний угол и увеличим в размерах. Также, добавим TextView, которого не было в первой сцене.
<FrameLayout
android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/transition_square"
android:layout_width="@dimen/square_size_expanded"
android:layout_height="@dimen/square_size_expanded"
android:background="@color/square_bg"
android:layout_gravity="top|right"/>
<TextView
android:id="@+id/transition_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="This text fades in."
android:textAppearance="?android:attr/textAppearanceLarge"/>/>
</FrameLayout>
В onCreate у Activity опишем:
ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.scene_root);
// You can also inflate a generate a Scene from a layout resource file.
final Scene scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene3, this);
findViewById(R.id.btn_change_scene).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// опишем свой аналог AutoTransition
TransitionSet set = new TransitionSet();
set.addTransition(new Fade());
set.addTransition(new ChangeBounds());
// выполняться они будут одновременно
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
// уставим свою длительность анимации
set.setDuration(500);
// и изменим Interpolator
set.setInterpolator(new AccelerateInterpolator());
TransitionManager.go(scene2, set);
}
});
Результат:

Легко понять, почему этим API ещё мало кто пользуется и даже мало кто про него знает. Сейчас то время, когда разработчики только недавно осознали моральное право отказаться от поддержки Android 2.x и большинство новых проектов наконец начинаются с поддержкой minSdk Android 4.0. Но пока мало смысла использовать API, которое будет работать только начиная с 4.4 ещё, ведь большинство девайсов ещё на более старых версиях.
Но! Transition API уже можно использовать для Android 3.0+ и везде анимация будет работать одинаково. Для этого я разрабатываю библиотеку, которую назвал Transitions Everywhere [1]. Она является бэкпортом Transition API на более старые версии Android. Для интеграции в свой проект требуется подключить библиотеку как gradle зависимость:
dependencies {
compile "com.github.andkulikov:transitions-everywhere:1.2.0"
}
И для всех классов, связанных с этим API, заменить импорт на пакет android.transitions.everywhere.* вместо android.transition.*
Когда я начал интересоваться этой темой я нашел две похожие библиотеки:
github.com/guerwan/TransitionsBackport [2]
github.com/pardom/TransitionSupportLibrary [3]
Что их объединяет, так это то, что их создатели, видимо, забили на их развитие и обе они не реализовали до конца все, что можно было бы реализовать для обратной совместимости некоторых функциональностей на старых версиях Android. Я взял их за основу и добавил много нового. Различные исправления некоторых некорректных поведений, которые проявлялись, например на версиях до 4.3. К тому же, API моей библиотеки совместимо с версиями Android 2.2+, но на версиях до 3.0 смена сцен(layout) будет выполняться без анимации. А так же я стараюсь идти в ногу со временем. Не так давно выпустили новую версию Android 5.0 (Lollipop) и код всего пакета transitions API в ней, также, во многом изменился. Я смержил все изменения в свою библиотеку.



Для любого Transition можно задать множество элементов — целей, к которым оно будет применяться.
Это делается с помощью методов класса Transition:
Методы удаления ранее добавленных целей:
Методы, с помощью которых можно применять действие «для всех, кроме»:
Методы, с помощью которых можно исключать всех детей некого layout:
Сами Transition можно загружать из xml файла. Его кладем в папку res/anim. Пример
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:app="http://schemas.android.com/apk/res-auto"
app:transitionOrdering="together"
app:duration="400">
<changeBounds/>
<changeImageTransform/>
<fade
app:fadingMode="fade_in"
app:startDelay="200">
<targets>
<target app:targetId="@id/transition_title"/>
</targets>
</fade>
</transitionSet>
Мы создали набор операций TransitionSet, состоящий из трех других Transition. Заодно применили тут ранее не рассмотренное свойство startDelay — время задержки перед началом выполнения анимации. Аналог для вызова из кода — Transition.setStartDelay(long startDelay).
Можно загружать из xml файла целый TransitionManager, в котором описывать разные правила для смен сцен в зависимости от того, в каком порядке они происходят. Простой пример:
<?xml version="1.0" encoding="utf-8"?>
<transitionManager xmlns:app="http://schemas.android.com/apk/res-auto">
<transition
app:toScene="@layout/scene3"
app:transition="@anim/scene3_transition"/>
</transitionManager>
Примечание!
Два рассмотренных выше примера подправлены для работы с моей библиотекой Transitions Everywhere. Если использовать системные Transition API, то сами xml файлы нужно класть в директорию res/transition и для всех атрибутов необходимо использовать namespace android: вместо app:
Чтобы ещё больше убедиться какая крутая штука эти Transitions, давайте напишем свой свой собственный Transition.
Допустим, мы хотим анимировано изменять размер шрифта у TextView.
От нас требуется переопределить методы captureStartValues, captureEndValues и createAnimator. Первые два отвечают за снятие значений необходимых параметров View до и после смены сцен. А метод createAnimator, собственно, создает аниматор для этого изменения параметров.
private class TextSizeTransition extends Transition {
// имя параметра, для сохранения его в HashMap значений
private static final String PROPNAME_TEXT_SIZE = "textSizeTransition:textSize";
private void captureValues(TransitionValues transitionValues) {
// нас интересуют только TextView
if (transitionValues.view instanceof TextView) {
TextView textView = ((TextView) transitionValues.view);
// сохраняем значение размера шрифта, заранее приведя его в dip
transitionValues.values.put(PROPNAME_TEXT_SIZE, textView.getTextSize() /
getResources().getDisplayMetrics().density);
}
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
// применяем аниматор только тогда, когда у нас были сняты значения и до и после
// смены сцен и мы имеем дело с TextView.
// endValues будет равно null, например, если во второй сцене эта View исчезает
if (startValues != null && endValues != null && endValues.view instanceof TextView) {
TextView textView = (TextView) endValues.view;
// берем значения
float start = (Float) startValues.values.get(PROPNAME_TEXT_SIZE);
float end = (Float) endValues.values.get(PROPNAME_TEXT_SIZE);
if (start != end) {
// сначала возвращаем во View изначальный размер
textView.setTextSize(start);
// и анимируем его до требуемого
return ObjectAnimator.ofFloat(textView, "textSize", start, end);
}
}
return null;
}
}
Результат:

Давайте анимировать Android!
Ещё раз ссылка на библиотеку:
github.com/andkulikov/transitions-everywhere [1]
Спасибо за внимание.
Автор: andkulikov
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android-development/74988
Ссылки в тексте:
[1] Transitions Everywhere: https://github.com/andkulikov/transitions-everywhere
[2] github.com/guerwan/TransitionsBackport: https://github.com/guerwan/TransitionsBackport
[3] github.com/pardom/TransitionSupportLibrary: https://github.com/pardom/TransitionSupportLibrary
[4] примере к Android 4.4: https://developer.android.com/downloads/samples/FragmentTransition.zip
[5] Источник: http://habrahabr.ru/post/243363/
Нажмите здесь для печати.