Создание анимаций переходов между Activity в Android

Начиная с Android 4.4 в арсенале разработчиков появился дополнительный инструмент для создания анимаций — Transitions Framework. Изначально он предназначался для создания анимаций изменения состояния приложения путём манипулирования несколькими View. С выходом Android 5.0 набор доступных для использования анимаций был расширен, чтобы соответствовать представленной тогда же концепции Material Design.

Transitions Framework позволяет быстро и безболезненно создавать различные анимации. Поэтому в процессе работы над iFunny было невозможно пройти мимо этого инструментария. Вниманию читателей предлагается частный случай использования Transitions API — создание анимации перехода между Activity с эффектом «бесшовности».

image


С визуальной точки зрения представленные в Transitions Framework анимации переходов между Activity можно условно разделить на два типа: обычные анимации и анимации с общим элементом. Концепт анимации с общим элементом продемонстрирован на честно украденном с сайта developer.android.com рис. 1. На нём в роли общих элементов выступают аватар и имя контакта.

image


Рис. 1. Анимация перехода между Activity с общими элементами

Но никто не любит длинные вступления, поэтому сразу перейдём к рассказу о том, как создавались анимации данного типа в приложении iFunny. В качестве первого примера рассмотрим анимацию, показанную на рис. 2. Для её использования нам потребуется Android версии 5.0 и выше.

image


Рис. 2. Анимация перехода между Activity на экране аутентификации пользователя

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

Первым шагом к созданию подобного перехода является, как ни странно, выбор этого самого элемента и определение его местоположения в вёрстке обеих Activity. После этого в описание каждого View, отображающего выбранный элемент, нужно добавить атрибут android: transitionName, а также назначить им android: id, если таковой отсутствует.

В нашем случае это обычные ImageView следующего вида:



Здесь стоит отметить два важных момента. Во-первых, в обоих ImageView необходимо установить одинаковые transitionName, что логично. Во-вторых, коль скоро мы используем ImageView, то и содержимое у них должно быть одним и тем же, поскольку использование двух отличающихся ресурсов может привести к неожиданным последствиям (как минимум к морганию анимируемого View в начале и конце анимации).

На втором шаге необходимо добавить опции для запускаемой (второй) Activity, сообщающие о том, что при её запуске должна быть запущена анимация.

Примечание. Под «второй» подразумевается запускаемая Activity, переход к которой должен быть осуществлён, а под «первой» — запускающая Activity.

Делается это следующим образом:

Bundle bundle = null;

if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
  View v = activity.findViewById(R.id.auth_logo);
  if (v != null) {
     ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(activity, v, activity.getString(R.string.email_auth_transition));
     bundle = options.toBundle();
  }
}

Intent intent = new Intent(activity, SecondActivity.class);
if (bundle == null) {
  activity.startActivity(intent);
} else {
  activity.startActivity(intent, bundle);
}


В приведённом листинге:

  • R.id.auth_logo — ImageView из первой Activity, используемый в анимации;
  • activity — первая Activity;
  • R.string.email_auth_transition — метка, ранее оставленная в вёрстке обоих ImageView;
  • SecondActivity.class — вторая Activity.


И сейчас внимательный читатель может испытать недоумение: во вступлении речь шла об использовании API level 19, в примере фигурировал API level 21, а в листинге выше стоит ограничение на API level 22. К сожалению, при написании кода выяснилось, что анимации перехода с общим элементом могут вести себя некорректно на телефонах c API level 21. Проявляется это в виде подтормаживаний анимации в целом и артефактов на анимируемом View в частности. Если вы уже знакомы с темой, знаете причины подобного поведения и/или способы решения описанной проблемы — расскажите нам об этом в комментариях.

На третьем шаге необходимо описать анимацию перехода, т.е. указать путь, проходимый анимируемым View, и трансформацию самого View. Для этого создадим отдельный файл projectName/src/main/res/transitions/email_auth_transition.xml со следующим содержимым:



	
	


Немного теории. Тег transitionSet предназначен для описания сразу нескольких трансформаций, применяемых к анимируемому View. Параметр transitionOrdering отвечает за порядок применения этих трансформаций. В нашем случае они применяются одновременно. Существует несколько типов готовых трансформаций, представленных в Transitions Framework. С полным списком можно ознакомиться на этой странице. Мы же остановимся на двух конкретных: changeBounds и changeImageTransform.

Первая предназначена для трансформации размера View. Вторая работает только с ImageView и в связке с первой позволяет изменять не только размер, но и форму ImageView. Использовав данные трансформации, получаем на выходе анимацию изменения размера изображения, представленную на рис. 2. Если не указывать тип движения анимируемого View, то он будет двигаться по кратчайшему пути. Более интересный способ передвижения рассмотрим во втором примере.

Последним шагом создания анимации является её объявление в темах обеих Activity. Для этого отредактируем описание тем следующим образом (или создадим новые в папке projectName/src/main/res/values-v22/theme.xml):





Здесь:

  • android: windowActivityTransitions разрешает выполнение анимации перехода;
  • android: windowSharedElementEnterTransition указывает на файл с описанием анимации перехода от первой Activity ко второй;
  • android: windowSharedElementExitTransition указывает на файл с описанием анимации перехода при возвращении из второй Activity в первую.


Следует отметить, что для версий ОС ниже 5.1 необходимо создать темы с идентичными стилями, чтобы избежать вполне ожидаемых последствий в виде падения приложения. Например, поместим их в файл projectName/src/main/res/values/theme.xml: