Transitions API: делаем анимацию для Android приложений

автор Сергей Григорьев

21 мая 2015

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

В то время как iOS давно предоставляет удобные инструменты для анимирования интерфейсов, в Android решения, позволяющие оптимизировать труд разработчиков, появились относительно недавно. Однако с ними жизнь специалистов определенно заиграла новыми красками.

Инструменты, о которых мы расскажем сегодня, — это удобное решение для самых разных анимаций: вместо отдельных экранов программист анимирует так называемые сцены, а переходы между ними генерируются автоматически при помощи Transition API — и это лишь верхушка айсберга возможностей. И если вы еще не используете эту технологию на своих проектах — самое время попробовать!

Transitions API: как это работает?

Уже в Android 4.0 существовал ранний вариант решения проблемы с анимацией — флаг animateLayoutChange для ViewGroup. Однако этот инструмент был недостаточно гибким и не мог обеспечить разработчику полный контроль над переходами (Transitions). В Android 4.4 KitKat и выше были реализованы Transitions API. Поскольку Transitions API также есть в саппорт-библиотеке, теперь с их помощью можно упростить работу с анимацией практически на любом девайсе под Android.

Именно в KitKat Transition API появляется такое понятие как сцена — Scene, и Transition — переход между сценами. Для определения корневого layout вводится понятие Scene root, внутри которого и происходит изменение сцен. При этом сама сцена по сути является враппером над ViewGroup, описывающим своё состояние и состояние объектов View в нем.

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

Transition Framework предоставляeт следующие возможности для создания анимаций:

  • Group-level animations: возможность анимировать целые иерархии объектов View. Разработчик указывает ViewGroup, которую нужно санимировать, и анимации автоматически применяются к каждому её элементу;
  • Transition-based animation: анимации, основанные на переходах;
  • Built-in animations: простые встроенные анимации, которые генерируются автоматически, такие как растворение, затемнение, изменение размера, движение и т.д.;
  • Resource file support: поддержка файлов ресурсов, в которых может быть записано, что и как анимировать. Таким образом, необязательно прописывать все анимации в коде;
  • Lifecycle callbacks: предоставляет все необходимые методы контроля за воспроизведением анимации;

При всех своих достоинствах данный метод создания анимаций имеет и некоторые ограничения:

  • Может давать сбои, если применяется к наиболее сложным SurfaceView и TextureView, работающим не в UI потоке, что ведет к рассинхронизации анимации.
  • Плохо работают AdapterView, такими как ListView, когда в них необходимо анимировать отдельные элементы.
  • Периодически возникают проблемы с синхронизацией при попытке изменить размер TextView: шрифт может отобразиться в следующей сцене до того, как закончилось изменение остальных объектов.

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

Рассмотрим примеры работы с Transition Frameworks

Создание сцены из ресурса:

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/scene_first" />
    </FrameLayout>
</LinearLayout>

res/layout/scene_first.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/text_view1
        android:text="Text Line 1" />
    <TextView
        android:id="@+id/text_view2
        android:text="Text Line 2" />
</RelativeLayout>

res/layout/scene_second.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/text_view2
        android:text="Text Line 2" />
    <TextView
        android:id="@+id/text_view1
        android:text="Text Line 1" />
</RelativeLayout>
Scene mFirstScene;
Scene mSecondScene;

// Create the scene root for the scenes in this app
mSceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes
mFirstScene = 
    Scene.getSceneForLayout(mSceneRoot, R.layout.scene_first, this);
mSecondScene =
    Scene.getSceneForLayout(mSceneRoot, R.layout.scene_second, this);

Создание сцены из кода:

// Obtain the scene root element
mSceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered
mViewHierarchy = (ViewGroup)findViewById(R.id.scene_conteiner);

// Create a scene
Scene scene = new Scene(mSceneRoot, mViewHierarchy);

Сами Transitions также создаются на базе ресурса:

res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

Transition mFadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

Или кода:

Transition mFadeTransition = new Fade();

Есть возможность создавать целые сеты анимаций, например, одновременное движение, изменение размера и затемнение

В ресурсе:

<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 set = new TransitionSet();
set.addTransition(new Fade())
    .addTransition(new ChangeBounds())
    .addTransition(new AutoTransition());

Еще одна возможность — применить анимацию не ко всей сцене, а к отдельному объекту View

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds />
    <fade android:fadingMode="fade_in" />
        <targets>
            <target android:targetId="@id/transition_title" />
        </targets>
    </fade>
</transitionSet>

Transition manager создается одной строчкой кода. Он существует для того, чтобы прописать все сцены и переходы в одном месте. Это позволяет существенно сократить объем работ и упростить дальнейший контроль над анимацией:

res/transition/transition_manager.xml

<transitionManager xmlns:app="http://schemas.android.com/apk/res-auto">
    <transition
        app:fromScene="@layout/scene_reg1"
        app:toScene="@layout/scene_reg2"
        app:transition="@transition/trans_reg1_to_reg2" />
    <transition
        app:fromScene="@layout/scene_reg2"
        app:toScene="@layout/scene_reg3"
        app:transition="@transition/trans_reg2_to_reg3" />
    ...
</transitionManager>
TransitionManager transitionManager = TransitionInflater.from(context)
    .inflateTransitionManager(R.transition.transition_manager, sceneRoot);

Как запускать сцены? Очень просто!

С кастомными Transitions:

mTransitionManager.transitionTo(scene);

или

TransitionManager.go(scene, fadeTransition);

C Transitions по умолчанию:

TransitionManager.go(scene);

Или без Transitions:

scene.enter();

Использовать переходы можно и отдельно, не создавая сцены

res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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" />
    ...
</RelativeLayout>

MainActivity.java

private TextView mLabelText;
private Fade mFade;
private ViewGroup mRootView;

// Load the layout
setContentView(R.layout.activity_main);

// Create a new TextView and set some View properties
mLabelText = new TextView();
mLabelText.setText("Label").setId("1");

// Get the root view and create a transition
mRootView = (ViewGroup) findViewById(R.id.mainLayout);
mFade = new Fade(IN);

// Start recording changes to the view hierarchy
TransitionManager.beginDelayedTransition(mRootView, mFade);

// Add the new TextView to the view hierarchy
mRootView.addView(mLabelText);

// When the system redraws the screen to show this update,
// the framework will animate the addition as a fade in

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

public static interface TransitionListener {
   void onTransitionStart(Transition transition);

   void onTransitionEnd(Transition transition);

   void onTransitionCancel(Transition transition);

   void onTransitionPause(Transition transition);

   void onTransitionResume(Transition transition);
}

Помимо встроенных вы можете создавать и собственные анимации. Например, так можно изменить цвет фона View:

public class ChangeColor extends Transition {

   private static final String PROPNAME_BACKGROUND = 
"customtransition:change_color:background";

   private void captureValues(TransitionValues values) {
       values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
   }

   @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) {
       if (null == startValues || null == endValues) {
           return null;
       }
      
       final View view = endValues.view;
      
       Drawable startBackground = 
           (Drawable) startValues.values.get(PROPNAME_BACKGROUND);
       Drawable endBackground = 
           (Drawable) endValues.values.get(PROPNAME_BACKGROUND);
      
       ColorDrawable startColor = (ColorDrawable) startBackground;
       ColorDrawable endColor = (ColorDrawable) endBackground;

       if (startColor.getColor() == endColor.getColor()) {
           return null;
       }

       ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
               startColor.getColor(), endColor.getColor());
       animator
           .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               Object value = animation.getAnimatedValue();
               if (null != value) {
                   view.setBackgroundColor((Integer) value);
               }
           }
       });
       return animator;
   }

}

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

Почему это важно?

Ускорение и упрощение процесса создания анимации добавляет драйв в разработку мобильных приложений. Команда Azoft отнеслась к Transitions API с энтузиазмом: мы уже используем их в своих проектах. Возможность создавать анимацию при помощи сцен, экономя время и силы, оказалась полезной как нашим разработчикам, так и клиентам, которые теперь могут быстрее получить готовое приложение.

Расскажите нам о своем опыте создания анимации для Android: используете ли вы Transitions API? Какие есть плюсы и минусы у этого и других методов, с которыми вы работали?

  • 0 Репосты

Комментарии

Фильтр

Закрыть

Технологии

Индустрии