使用Transitions API为安卓应用创建动画

英文原文:http://cases.azoft.com/create-android-app-animations-using-transitions-api/

Create android app animations using transitions api

为安卓创建动画

虽然移动应用对动画的依赖相当普遍,但在安卓系统中开发者认为创建一个动画的过程一直都是一种挑战。ios早就提供了处理动画的实用工具,而安卓上帮助开发者提高工作效率的解决方案还相对较新。

这些动画工具大大的节省了程序员的时间。 使用这些工具实现 creating a variety of app animations 非常方便。和对不同屏幕应用动画不同,开发者可以使用Transition API制造所谓的“场景动画”,Transition API自动产生过渡效果,而这只是冰山一角。我相信这篇文章所要分享的知识对安卓开发者会非常有用。

Transitions API:是如何工作的?

即使在Android4.0上,也有一个解决动画问题的早期办法:针对ViewGroup的布局动画。但是这个工具不够灵活,开发者无法完全掌控动画的过程。不过从Android 4.4 KitKat开始,有了Transitions API,在兼容包中也有Transitions API,因此可以在几乎所有的安卓设备上使用。(这点我大大的怀疑,也许兼容包中是有Transitions API,但是只是做到不出错,兼容而已,根本没有动画效果,不知道新的兼容包是否有改进)。

在KitKat Transition API中,出现了诸如场景(Scene)、场景过渡(Transition)这样的概念,为了决定root  layout,场景Scene root被引入,场景中的所有变化都发生在Scene root中。同时,场景自身本质上是对ViewGroup的一层封装,描述了自己以及所有View对象的的状态。而过渡(Transition)则是一种这样的机制:读取不同场景之间View属性的变化,从而产生让这种变化看起来平滑的动画。

KitKat中Transition框架的Transition API为创建动画提供了如下特色:

组级别的动画:可以将整个View树作为整体动画,你只需指定ViewGroup,它的各个元素就会自动应用动画。

基于Transition的动画

内置动画:内置简单的动画,比如dissolution,darkening,resizing,movement等等。

对资源文件的支持:你可以不必写代码,在资源文件中创建动画。

回调:提供掌控动画过程的所有必要的回调方法。

原文此处有youtube视频

虽然有如此多的优点。但是这个新的api也有一些局限:

在那些不在UI线程中工作的View,比如SurfaceView或者TextureView中使用的时候不流畅。

AdapterView,比如ListView,当你需要针对列表中某个单独的元素使用动画的时候。

偶尔会在resize TextView的时候会出现同步的问题:在另一个对象的改变结束的之前,字体会提前出现在下一个场景中。

但是,这些局限都不是很关大局。在实际开发中,需要在SurfaceView中应用动画的类似情况非常少见。

考虑下面Transition框架的动画例子:

通过资源文件创建一个场景:

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);

通过资源文件创建Transition:

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();

你还可以创建整个动画集。比如:move,resize,darken无缝的结合在一起:

在资源文件中:

<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。需要将所有的场景和Transition都写在一个地方。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>

如何运行场景? 简单!

使用自定义的Transition:

mTransitionManager.transitionTo(scene);

或者

TransitionManager.go(scene, fadeTransition);

使用默认的Transition:

TransitionManager.go(scene);

或者根本没有Transition:

scene.enter();

你也可以无需创建场景就使用Transition:

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的背景颜色:

原文此处有youtube视频

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;
   }
}

自动产生中间值,这就是为什么我们的例子中颜色逐渐从红色变到蓝色。这个方法打开了创建多种自定义动画与过渡的可能性:只有想不到,没有做不到。

为什么我们要关心Transition?

关于创建动画的快速与简便请了解development of mobile apps。Azoft团队对Transitions API非常热情,我们已经在项目中使用了这个方法。使用Scene创建动画节省了很多时间和精力,这对开发者和急于看到结果的客户来说都是好事。

告诉我们关于你为安卓创建动画的经历。你们使用Transitions API吗?除此之外你们还使用什么工具来为移动应用创建动画?