Пожалуйста, опишите ошибку

Нашли баг? Помогите нам его исправить, заполнив эту форму

Анимация в Android: как это работает

Илья Михайленко
Android-разработчик

Всем привет! Хочу рассказать об анимациях в Android. А конкретно о Transition Api и замечательной библиотеке TransitionEverywhere.

Transition Api включён в стандартный android sdk. Проблема в том, что большинство возможностей Transition Api доступно только для api 21+. Недавно Google забэкпортил многие возможности Transition Api для api 19+, но этого всё равно может быть недостаточно. На выручку нам приходит либа Transition Everywhere (https://github.com/andkulikov/Transitions-Everywhere), которая является бэкпортом гугловских Transitions для более ранних версий android, вплоть до android 4.0 (api 14). Кроме того, библиотека включает в себя ещё различные фичи.

Кратко подытожу: если разрабатываете приложение для api 19 и не хотите подключать ничего дополнительно, то можно обойтись Transition Api, включённым в sdk, если же вам нужна поддержка более ранних версий, то придётся использовать дополнительную либу, Transition-Everywhere (ссылка выше). Подключается она одной строчкой:

dependencies {
    compile "com.andkulikov:transitionseverywhere:1.7.0"
}

Чем же эта Transition Api хороша? Тем, что она просто в использовании и с её помощью можно реализовать большинство необходимых анимаций. Для реализации некоторых анимаций нужно написать всего одну строчку! Да и реализовывать более сложные анимации тоже одно удовольствие 🙂

Наконец, начнём.

Рассмотрим метод beginDelayedTransition у TransitionManager. Первым параметром идёт корневая View, к членам которой мы хотим применить анимацию, включая саму корневую вьюху. Вторым параметром мы указываем тип анимации, которую надо применить. Если второй параметр пропущен для будет установлен дефолтный AutoTransition. После вызова этого метода все View внутри указанного 1м параметром корневого View будут «слушать» действия, запускающие анимацию.

Реализуем анимацию исчезновения текста. Сначала укажем View контейнер, в котором лежит View, которую нам нужно анимировать. В нашем случае transitionContainer — это RelativeLayout, а text — это TextView, лежащая внутри этого layout. Второй параметр пропустим. Затем меняем свойство видимости у text, что запускает процесс анимации.

TransitionManager.beginDelayedTransition(transitionsContainer);
visible = !visible;
text.setVisibility(visible ? View.VISIBLE : View.GONE);

Результат:

Так как мы не указали 2й параметр в beginDelayedTransition, к text была применена AutoTransition. Он включает в себя анимации Fade и ChangeBounds. Рассмотрим пример с типом анимации Slide. Сделаем так, чтобы текст ускользил за пределы экрана и исчез. Для этого укажем вторым параметром в методе beginDelayedTransition наш тип Slide и укажем направление скольжения, с помощью Gravity. Затем изменим свойство видимости у text.

TransitionManager.beginDelayedTransition(transitionsContainer, new Slide(Gravity.RIGHT));
visible = !visible;
text.setVisibility(visible ? View.VISIBLE : View.GONE);

Результат:

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

mToRightAnimation = !mToRightAnimation;
Transition transition = new ChangeBounds();
transition.setDuration(mToRightAnimation ? 700 : 300);
transition.setInterpolator(mToRightAnimation ? new FastOutSlowInInterpolator() : new AccelerateInterpolator());
transition.setStartDelay(mToRightAnimation ? 0 : 500);

Затем в вызове метода beginDelayedTransition укажем 2м параметром определённую нами анимацию и определим изменение Layout параметров у нашей кнопки.

TransitionManager.beginDelayedTransition(transitionsContainer, transition);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)button.getLayoutParams();
params.gravity = mToRightAnimation ? (Gravity.RIGHT | Gravity.TOP) : (Gravity.LEFT | Gravity.TOP);
button.setLayoutParams(params);

Результат:

Рассмотрим ещё одну интересную анимацию, которую можно сделать, определив у ChangeBounds движение по пути методом setPathMotion.

TransitionManager.beginDelayedTransition(transitionsContainer,
        new ChangeBounds().setPathMotion(new ArcMotion()).setDuration(500));

mToRightAnimation = !mToRightAnimation;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) button.getLayoutParams();
params.gravity = mToRightAnimation ? (Gravity.RIGHT | Gravity.BOTTOM) :
        (Gravity.LEFT | Gravity.TOP);
button.setLayoutParams(params);

Результат:

Реализуем анимацию изменения цвета. Для этого нам нужна анимация типа Recolor.

TransitionManager.beginDelayedTransition(transitionsContainer, new Recolor());
    mColorsInverted = !mColorsInverted;
    button.setTextColor(getResources().getColor(!mColorsInverted ? R.color.second_accent : R.color.accent));
    button.setBackgroundDrawable(
        new ColorDrawable(getResources().getColor(!mColorsInverted ? R.color.accent : R.color.second_accent)));

Результат:

Реализуем анимацию поворота иконки. Для этого будем использовать анимацию типа Rotate.

TransitionManager.beginDelayedTransition(transitionsContainer, new Rotate());
mRotated = !mRotated;
icon.setRotation(mRotated ? 135 : 0);

Результат:

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

Посмотрим, как применить несколько анимаций к одному объекту. Реализуем исчезновения текста одновременно с его уменьшением. Определим множество анимацию TransitionSet, включив в него анимацию Scale с коэффициентом уменьшение 0.7 и анимацию Fade.

TransitionSet set = new TransitionSet()
        .addTransition(new Scale(0.7f))
        .addTransition(new Fade())
        .setInterpolator(visible ? new LinearOutSlowInInterpolator() : new FastOutLinearInInterpolator());
TransitionManager.beginDelayedTransition(transitionsContainer, set);
text2.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);

Результат:

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

Это делается с помощью методов класса Transition: addTarget — добавить цель, removeTarget — удаление раннее выбранной цели, excludeTarget — метод, с помощью которого можно применять действие «для всех, кроме» и excludeChildren с помощью которого можно исключать детей некоторого layout.

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

TransitionManager.beginDelayedTransition(mRecyclerView,  new Explode());
// remove all views from Recycler View
mRecyclerView.setAdapter(null);

Результат:

А теперь сделаем так, чтобы на ячейку, на которую мы нажали не действовала анимацию Explode, а действовала анимация Fade. Для этого создадим множество из 2х анимаций Explode и Fade. Для Explode с помощью метода excludeTarget исключим нажатую ячейку, а для метода Fade методом addTarget добавим только одну цель — нашу нажатую ячейку.

final Rect viewRect = new Rect();
clickedView.getGlobalVisibleRect(viewRect);
TransitionSet set = new TransitionSet()
        .addTransition(new Explode().setEpicenterCallback(new Transition.EpicenterCallback() {
            @Override
            public Rect onGetEpicenter(Transition transition) {
                return viewRect;
            }
        })
                .excludeTarget(clickedView, true))
        .addTransition(new Fade().addTarget(clickedView));
TransitionManager.beginDelayedTransition(mRecyclerView, set);

Результат:

Теперь нажатая ячейка остаётся на месте и плавно исчезает, а остальные ячейки разбегаются.

Надеюсь, было полезно. Спасибо за внимание!

Читать и комментировать