使用Scenes和Transitions执行动画 -- Animating Views Using Scenes and Transitions

当用户通过键盘输入或者触发其他事件时界面需要做出变化。比如,某个Activity包含一个搜索框,当用户输入数据并提交的时候,Activity会隐藏搜索框同时显示搜索的结果。

在这种应用场景下你可以通过在不同的View树运行动画,来提供连续的视觉效果。动画不仅仅响应了用户的操作也帮助用户学习应用是如何工作的。

Android的Transitions 框架让开发者可以方便的实现两个View树之间的动画。Transitions 框架是通过动态的改变View的属性从而达到动画变换的效果。Android系统内置了常用的动画效果,开发者也可以自定义动画和变换(的生命周期)。

本文介绍如何使用transitions 框架内置的动画,当然你也可以创建自定义动画(和动画生命周期的回调)。

  • 注意:Android版本在14(4.0)-19(4.4.2)之间的用户,使用animateLayoutChanges属性设置布局动画。了解更多请查看属性动画。


一.Transitions框架--The Transitions Framework

在你的应用中加入动画不仅仅是增加了视觉的吸引力,动画所强调的变化和视觉线索可以帮助用户学习如何使用你的应用。
Android系统提供了Transitions 框架,使得你可以在两个不同的View树之间运行动画。当动画在两个不同的View树间运行时,Transitions 框架将对View树中所有的View执行一个或者多个动画。
框架的特性:
  • Group级的动画:对View树中的所有View执行一个或多个动画
  • 基于变化的动画:动画的运行是基于View属性的开始值和结束值
  • 内置动画:包含了一些预置的动画,比如淡入淡出和移动
  • 支持资源文件:可以在布局文件中加载内置的动画
  • 生命周期回调:定义了回调函数,在动画的执行过程中进行控制

  
1.概述
Transition框架中执行动画会改变两个View树中的所有View。View树可以是一个简单的View也可能是一个类似ViewGroup包含一个树状结构的视图。从动画的初始状态(View开始树)到终止状态(View结束树),每个View的一个或多个属性在动画的运行过程中都会发生变化。
Transition框架与View树和动画是平行的关系。Transition框架主要作用是存储View树的状态,切换View树以改变屏幕的显示效果,并在切换View树的时候执行定义的动画。
图二演示了View树,Transition框架和动画之间的关系:



Transition框架提供了抽象的Scenes,Transition,Transition managers。使用Transition框架,首先为即将运行动画的View树创建Scenes。然后,为动画创建Transition。启动两个View树间的动画,使用Transition Manager对象,传入指定的Transition和结束Scene。

2.场景 -- Scenes:
Scene存储了View树的状态,包括View树中的所有View和这些View的属性。View树可以是简单的View也可能是复杂的ViewGroup或者layout。Scene中存储了View树的状态,使得你可以从另一个Scene变换到当前的Scene
创建Scene可以通过资源文件或者在代码中使用ViewGroup对象。通过代码创建Scene的好处就是View树可以动态的生成并且在运行过程也可以被修改。
通常情况下,你并不需要创建开始的场景。当你使用一个Transition时,系统会使用上一个Transition的结束Scene作为下一个Transition的开始Scene。如果你没有使用Transition,系统则会收集View的当前状态作为开始的场景。
Scene变换时,也可以定义它的运行动作。例如,这一特性用于在Scene变换后清除View的设置。
Scene除了存储了View树和View树的属性值,同时也存储了View树的父视图的引用。这个父视图叫Scene Root。Scene的变换和动画都发生在Scene Root中。

3.Transition
在Transition框架中,动画创建了一系列的帧,描述了View树从开始Scene到结束Scene的变化。动画的信息都存储在Transition对象中。可以使用TransitionManager对象为动画指定一个Transition。Transition可以用于两个不同的Scene,或者是同一Scene的不同状态(译者注:View属性的不同)。

系统内置了一组常用动画的Transition,比如淡入淡出,缩放等。你可以根据框架中提供的API自定义一个Transition来创建想要动画的效果。同样你也可以组合不同的动画效果到一个包含自定义或者内置Transition的Transition集合中。
系统监控Transition生命周期,从动画的开始到动画的结束,这一点和Activity相似。每个重要的生命周期状态,都会执行一个回调函数,在函数中你可以根据不同的状态调整用户界面。

4.限制 -- Limitations
Transition框架的限制:
  • 应用于SurfaceView的动画显示会有问题。SurfaceView的更新通过非UI线程,所以它的更新与动画中的其他View可能会不同步。
  • 应用于TextureView的某些特定Transition类型会产生一些不同与期望的动画效果。
  • 继承自AdapterView的类,例如ListView,管理子View的方式与Transition框架不兼容。如果将动画应用于这些View,会出问题。
  • TextView执行缩放动画时,在动画完成前,文本会被放置到新得到位置。为了避免此问题,不要在包含文本的TextView中使用缩放的动画。


二.创建场景--Creating a Scene

Scene存储了View树的状态,包括View树中的所有View和属性值。Transition框架可以在开始和结束的Scene中执行动画。开始Scene通常由用户界面的当前状态决定。结束Scene,可以通过资源文件或者使用代码创建。
本文介绍如何在你的应用中创建Scene并定义Scene Action。下一节介绍如何在两个Scene中切换。
注意:单个View树的变换可以不使用Scene。

1.通过资源文件创建场景 -- Create a Scene From a Layout Resource
通过资源文件可以直接创建Scene对象。当View树不会变化时可以使用这种方式。生成的Scene代表你创建Scene实例时View树的状态。如果改变了View树,必须重新创建Scene。创建的Scene包含整个资源文件,不允许从部分资源文件中创建Scene。
想要从布局文件中创建Scene,首先从布局文件中获取ViewGroup类型Scene Root对象,接着调用Scene.getSceneForLayout(),参数为Scene Root和布局文件中作为Scene的View树的资源ID。

为场景定义资源文件 -- Define Layouts for Scenes
接下来演示如何通过相同的Scene Root元素创建两个不同的Scene。通过代码同样可以看到你只需要加载两个不相关的Scene对象并不需要定义他们的依赖关系。
示例中的布局为:
  • Activity的主布局文件包含一个TextView和一个子布局
  • 一个相对布局中包含了两个TextView作为第一个Scene
  • 一个相对布局同样包含两个TextView但是顺序不同作为第二个Scene
示例中所有的动画发生在Activity主布局文件的子布局中,而主布局文件中的TextView是不变的。
Activity主布局文件为:
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/a_scene" />
    </FrameLayout>
</LinearLayout>
该布局文件中定义了一个TextView和一个作为Scene Root的子布局。第一个Scene被包含在主布局文件中。App把它作为应用的初始界面,Scene也会把整个子布局加载起来,因为Transition系统是不能够加载部分布局到Scene中。

Scene 1的布局定义如下:
res/layout/a_scene.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>

Scene 2包含相同的两个TextView(一样的资源ID),但是他们的顺序和Scene 1不一样:
res/layout/anther_scene.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>

从Layout文件中生成Scene -- Generate Scenes from Layouts
定义好两个布局文件后,就可以获取Scene对象了。这样你就能够在这两个UI配置间执行Transition动画。获取Scene对象通过Scene Root和布局的资源ID。
下面的代码演示了如何获取Scene Root的和通过布局文件创建Scene:
Scene mAScene;
Scene mAnotherScene;

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

// Create the scenes
mAScene = Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this);
mAnotherScene =
    Scene.getSceneForLayout(mSceneRoot, R.layout.another_scene, this);

这样,应用就包含了两个基于View树的Scene对象。两个Scene使用同样Scene Root,它通过activity_main.xml中的FrameLayout元素定义。
2.通过代码创建Scene
在代码中通过ViewGroup对象同样可以创建Scene实例。当你在代码中需要修改View树的结构或者动态的生成View树时,可以使用这种方式。
在代码中利用View树创建Scene,使用Scene(sceneRoot,viewHierarchy)构造函数。这个构造函数和Scene.getSceneForLayout()有同样的效果。
下面的代码演示了如何根据Scene Root元素和View树中创建Scene:
Scene mScene;

// Obtain the scene root element
mSceneRoot = (ViewGroup) mSomeLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered
mViewHierarchy = (ViewGroup) someOtherLayoutElement;

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

3.创建Scene动作 -- Create Scene Actions
当Scene进入和退出时,系统允许你自定义Scene Action。通常情况下自定义Action是没有必要的,因为系统会自动处理Scene间的变换。
Scene Action用于下面几个情况:
  • 执行动画的View不在同一个View树里,View执行动画的时机可能是在Scene开始或者结束时。
  • Transition框架不支持的View,比如ListView。
将自定义的Scene Action定义成Runnable对象,然后作为参数调用Scene.setExitAction()和Scene.setEnterAction()方法。在开始Scene运行Transition动画之前,系统会调用setExitAction()方法,结束Scene运行Transition动画之后会调用setEnterAction()方法。
注意:不要在结束Scene和开始Scene的View间使用Scene Action传递数据。因为结束View树直到动画结束才会被初始化。


三.使用Transition -- Applying a Transition

在Transition框架中,动画创建了一系列的帧描述View树从开始Scene到结束Scene之间的变化。这些变化在Transition框架中使用Transition对象来表示,所有动画的信息都包含在其中。启动动画使用Transition Manager,传入使用的Transition对象和结束的Scene。
接下来介绍在两个Scene间使用内置的Transition运行移动,缩放,淡入淡出等动画。

1.创建Transition -- Create a Transition
在上一节,介绍了如何创建代表不同View树状态的Scene对象。有了开始的Scene和结束的Scene之后就需要创建定义动画的Transition对象。内置Transition对象的创建方式有两种,可以资源文件定义,也可以直接用代码创建。

 内置的Transition类型
Class Tag Attributes Effect
AutoTransition <autoTransition/> - Default transition. Fade out, move and resize, and fade in views, in that order.
Fade <fade/> android:fadingMode="[fade_in |
fade_out |
fade_in_out]"
fade_in fades in views
fade_out fades out views
fade_in_out (default) does a fade_outfollowed by a fade_in.
ChangeBounds <changeBounds/> - Moves and resizes views.

在资源文件中创建Transition -- Create a Transition instance from a resource file
在资源文件中创建Transition的好处是当你需要修改Transition的定义的时候不用修改Activity中的代码。另一个好处就是将复杂的Transition定义和代码分离。
在资源文件中创建内置的Transition,遵循以下几步:
  1. 添加res/transition/目录到工程里
  2. 在该目录下创建XML文件
  3. 添加内置的Transition作为一个XML节点
例如,下面的资源文件声明了一个Fade Transition:
res/transition/fade_transition.xml
<fade xmlns:android="http://schemas.android.com/apk/res/android" />

接下来从资源文件中获取Transition对象
Transition mFadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

在代码中创建Transition -- Create a Transition instance in your code

这种方式的好处是可以动态的创建Transition对象(如果你在代码中需要修改用户界面),而且创建内置Transition需要很少的参数。
创建内置的Transition对象,通过调用Transition子类的public构造函数。如下所示:
Transition mFadeTransition = new Fade();

2.使用Transition -- Apply a Transition
Transition通常用于切换不同的View树,来响应用户操作等事件。例如:当用户输入搜索关键字并点击搜索按钮,应用切换到搜索结果布局,此时搜索界面执行淡出效果,搜索结果界面执行淡入效果。
在Activity中利用Transition切换Scene,通过调用静态方法TransitionManager.go(),并传入结束的Scene和代表动画效果的Transition实例,如下所示:

TransitionManager.go(mEndingScene, mFadeTransition);

当运行Transition实例指定的动画时,系统会将Scene Root中的View树切换成结束Scene中的View树。上一个Transition结束的Scene作为开始的Scene,如果不存在上一个Transition,用户界面的当前状态就是开始Scene。
如果没有指定Transition实例,TransitionManager会使用AutoTransition对象,它会执行大多数情况下合理的动画效果。更多的信息请参照TransitionManager类。


3.选择特定的Target View -- Choose Specific Target Views
默认情况下系统对开始和结束Scene中所有的View执行动画。有些时候,你或许希望仅仅让Scene中的一个子View运行动画。例如,系统不支持ListView对象的动画,在Transition的过程中就必须剔除ListView对象。系统允许你选定某个特定的View运行动画。
被选定运行动画的View叫Target。你只能选择Scene中View树的子View作为Target。
Target是被保存在列表中,从Target list中删除一个或者多个Target,调用removeTarget()方法,该方法必须在执行动画之前调用。调用addTarget()方法添加View到Target list中。更多信息请参照Transition类。

4.定义复杂的Transition -- Specify Multiple Transitions
动画的定义应该匹配Scene之间的变化。比如,Scene之间的变化是,删除一个View并添加另一个View,那么淡入/淡出动画可以更好的提示用户有些View不可见了。如果仅仅将View移动到另一个位置,那么使用移动动画告诉用户View移动到了新的位置则是更好的选择。
在Transition系统中可以绑定一系列内置或者自定义的动画到Transition集合中。
在XML中定义Transition集合,首先在/res/transitions/目录中创建资源文件,然后在transitionSet元素下定义Transition。下面的代码定义了一个和AutoTransition类相同行为的transition集合:
<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>

在代码中获取XML声明的TransitionSet对象,通过在Activity中调用TransitionInflater.from()方法。TransitionSet继承自Transition类,所以TransitionManager可以和使用Transition对象一样使用TransitionSet。

5.不使用Scene的情况下使用Transition -- Apply a Transition Without Scene
改变用户界面不仅仅只有通过切换View树这一种方式。你同样可以通过添加,修改,删除子View的方式。例如,你可以在一个布局文件中实现搜索的交互。首先在布局中显示搜索输入框和搜索按钮,接着当用户点击搜索按钮时,显示搜索结果同时通过ViewGroup.removeView()方法删除搜索按钮,添加搜索结果则通过ViewGroup.addView()方法。
如果供选择的两个View树有着相似的结构,则可以选择使用这种方式。不必为了用户界面的微小差别而创建和维护两个不同的资源文件,可以在资源文件中定义View树然后在代码中修改它的结构。
如果只是在一个View树改动,则不必创建Scene。而是使用delayed transition的方式在一个View树的两个状态间创建和使用Transition 。Transition框架记录View树的当前状态和View的变动,在系统重绘用户界面时对View的变化执行Transition动画。
在单一的View树中创建delayed transition,遵循以下步骤:
  1. 当事件触发了Transition,调用TransitionManager.beginDelayedTransition()方法,传入待执行动画View的父View和Transition。系统会保存View的当前状态和属性值。
  2. 根据你的需要改变子View。系统会记录哪些子View的哪些属性被改变了。
  3. 当系统根据你的变化重绘用户界面时,会在初始状态和最终状态间执行动画。
下面的代码演示了如何使用delayed transition添加一个TextView到一个View树中,首先资源文件如下所示:
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>

接下来的代码演示了在添加的TextView上执行动画:
MainActivity.java
private TextView mLabelText;
private Fade mFade;
private ViewGroup mRootView;
...

// Load the layout
this.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

6.定义Transition的生命周期回调 -- Define Transition Lifecycle Callbacks
Transition的生命周期和Activity相似。它代表从调用TransitionManager.go()方法到动画运行结束过程中的状态。重要的生命状态,系统会执行TransitionListener中的回调方法。
Transition的生命周期回调是非常有用的,比如,在Scene变化过程中将某个View的属性值从开始View树中复制到结束View树中。由于结束View树直到动画结束才会被初始化,所以简单的复制值会出问题。这种情况下,你可以先将值存储在一个变量中,然后当动画结束后再复制它到结束View树中。那么如何获取动画结束的通知,在Activity中实现TransitionListener.onTransitionEnd()即可。

更多信息,请参考TransitionListener类的API。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值