当用户通过键盘输入或者触发其他事件时界面需要做出变化,比如,某个Activity包含一个搜索框,当用户输入数据并提交的时候,Activity会隐藏搜索框同时显示搜索的结果。
在这种应用场景(Scenes)下,可以通过在不同的View树上运行动画,来提供连续的视觉效果。这些动画不仅仅响应了用户的操作,也帮助用户学习应用是如何工作的。
Android的过渡动画框架(Transitions Framework)可以方便的实现两个View树之间的动画。它通过动态的改变View的属性从而达到动画变换的效果。Android系统内置了常用的动画效果,也可以自定义过渡动画和过渡生命周期。
在Android4.4之前可以使用布局动画实现简单过渡动画效果,但是对于复杂的场景,布局动画使用起来不太方便,所以,Android4.4引入了过渡动画框架来方便实现这样的应用场景。
过渡动画的本质还是属性动画,只不过是对属性动画做了一层封装,目的是方便开发者实现ViewGroup的过渡动画效果。
场景和过渡动画
过渡动画框架可以在两个不同的View树之间运行过渡动画,它对View树中的所有View执行一个或者多个动画,框架的有如下特性:
- GroupView级别的动画:对View树中的所有View执行动画
- 基于变化的动画:动画的运行是基于View属性的开始值和结束值
- 内置动画:包含了一些预置的动画,比如淡入淡出和移动
- 支持资源文件:可以通过布局文件中加载View树和内置动画
- 声明周期回调:通过回调可以控制动画的执行过程
概述
过渡动画框架和动画的关系如下所示:
Transitions框架和View动画是平行关系。Transitions框架主要用于存储View树的状态,在切换View树时执行定义的动画,从而实现过渡效果。
Transitions框架提供了抽象的Scenes、Transition以及TransitionManagers。使用过程中,首先,为执行动画的View树创建初始Scene;然后,为动画创建Transition;最后,使用TransitionManager对象启动动画,并传入创建的Transition和结束Scene。
场景(Scenes)
Scene存储了View树的状态,即View树中所有View的属性值,这样就可以通过改变属性值,从当前Scene变换到指定的Scene。
创建Scene可以通过Layout文件或者在代码中使用GroupView对象。通过代码创建Scene,用于动态生成View树,或者在运行时改变View树。
通常情况下,并不需要创建开始Scene。当使用Transition时,系统会使用上一个Transition的结束Scene作为下一个Transition的开始Scene;如果之前没有使用过Transition,系统会收集View树的当前状态作为开始Scene。
可以定义Scene变化的Action。比如,在Scene变化后,清除View的设置。
Scene除了存储View树的属性值,同时也存储了View树的父视图引用,这个引用称为Scene Root,Scene的变换和动画都发生在Scene Root中。
过渡动画(Transition)
在Transitions框架中,创建了一系列的帧去描述View树从开始Scene到结束Scene的变化。动画的信息都存储在Transition对象中,可以使用TransitionManager对象为动画指定一个Transition。Transition可以用于两个不同的Scene,或者同一个Scene的不同状态。
系统内置了一组常用的Transition,比如淡入淡出,缩放等。可以根据框架中提供的API自定义一个Transition来创建想要动画的效果。也可以组合自定义或者内置Transition,作为一个Transition集合,达到不同的动画效果。
系统会监听Transition整个生命周期,这一点和Activity相似。每个重要的生命周期状态,都会执行一个回调函数,在函数中你可以根据不同的状态调整用户界面。
限制(Limitation)
- 应用于SurfaceView的动画显示会有问题。SurfaceView的更新通过非UI线程,它的更新与动画中的其他View可能会不同步。
- 用于TextureView的某些特定Transition类型会产生一些不同与期望的动画效果。
- 继承于AdapterView的类,例如ListView,管理子View的方式与Transition框架不兼容。如果将动画应用于这些View,会出问题。
- TextView执行缩放动画时,在动画完成前,文本会被放置到新得到位置。为了避免此问题,不要在包含文本的TextView中使用缩放的动画。
创建场景
Transitions框架可以在开始和结束的Scene中执行过渡动画。开始Scene通常由用户界面的当前状态决定。结束Scene,可以通过资源文件或者使用代码创建。
注意:单个View树的变换可以不使用Scene,具体使用在下一节介绍。
使用XML文件创建Scene
通过资源文件可以直接创建Scene对象。当View树不会变化时可以使用这种方式。生成的Scene代表当你创建Scene实例时View树的状态。如果改变了View树,必须重新创建Scene。创建的Scene包含整个资源文件,不允许从部分资源文件中创建Scene。
从布局文件中创建Scene,首先要从布局文件中获取ViewGroup类型Scene Root对象,接着调用Scene.getSceneForLayout(),参数为Scene Root和布局文件中作为Scene的View树的资源ID。
为Scene定义Layout
下面介绍通过相同的Scene Root元素创建两个不同的Scene。通过代码同样可以看到,只需要加载两个不相关的Scene对象,并不需要定义他们的依赖关系。
示例中的布局为:
- Activity的主布局文件包含一个TextView和一个子布局
- 一个相对布局中包含了两个TextView作为第一个Scene
- 一个相对布局同样包含两个TextView但是顺序不同作为第二个Scene
示例中所有的动画发生在Activity主布局文件的子布局中,而主布局文件中的TextView是不变的。
Activity主布局文件为:
1 | <!-- res/layout/activity_main.xml --> |
该布局文件中定义了一个TextView和一个作为Scene Root的子布局。第一个Scene被包含在主布局文件中。App把它作为应用的初始界面,Scene也会把整个子布局加载起来,因为Transition框架是不能够加载部分布局到Scene中。
Scene 1的布局定义如下:
1 | <!-- res/layout/a_scene.xml --> |
Scene 2包含相同的两个TextView(一样的资源ID),但是他们的顺序和Scene 1不一样:
1 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
从Layout文件中生成Scene
1 | Scene mAScene; |
这样,应用就包含了两个基于View树的Scene对象。两个Scene使用同样Scene Root,它通过activity_main.xml中的FrameLayout元素定义。
使用Java代码创建Scene
在代码中通过ViewGroup对象同样可以创建Scene实例。当你在代码中需要修改View树的结构或者动态的生成View树时,可以使用这种方式。
在代码中利用View树创建Scene,使用Scene(sceneRoot, viewHierarchy)构造函数。这个构造函数和Scene.getSceneForLayout()有同样的效果。
1 | Scene mScene; |
创建Scene Actions
当Scene进入和退出时,系统允许自定义Scene Action。通常情况下自定义Action是没有必要的,因为系统会自动处理Scene间的变换。
Scene Action用于下面几个情况:
- 执行动画的View不在Scene的View树里,其执行动画的时机可能在Scene开始或者结束时。
- Transitions框架不支持的View,比如ListView。
可以将Scene Action定义成Runnable对象,然后作为参数调用Scene.setExitAction()和Scene.setEnterAction()方法。在Transition动画运行之前,开始Scene会调用setExitAction()方法,在Transition动画运行之后,结束Scene会调用setEnterAction()方法。
注意:不要在开始Scene和结束Scene中的View之间使用Scene Action传递数据。因为结束View树直到动画结束才会被初始化。
使用过渡动画
在Transitions框架中,动画创建了一系列的帧描述View树从开始Scene到结束Scene之间的变化。这些变化在Transition框架中使用Transition对象来表示,所有动画的信息都包含在其中。通过TransitionManager对象启动动画,具体使用时,需要传入Transition对象和结束Scene。
创建Transition
内置Transition对象的创建方式有两种,可以资源文件定义,也可以直接用代码创建。
系统提供的内置Transition类型如下所示:
Class | Tag | Effect |
---|---|---|
AutoTransition | <autoTransition/> | Default transition. Fade out, move and resize, and fade in views, in that order. |
Fade | <fade/> | fade_in fades in views; fade_out fades out views; fade_in_out (default) does a fade_out followed by a fade_in. |
ChangeBounds | <changeBounds/> | Moves and resizes views. |
通过资源文件中创建Transition
在资源文件中创建Transition的好处是当你需要修改Transition的定义的时候不用修改Activity中的代码,另一个好处就是将复杂的Transition定义和代码分离。
- 创建Transition
1 | <!-- res/transition/fade_transition.xml --> |
- 从资源文件中获取Transition对象
1 | Transition mFadeTransition = |
通过代码中创建Transition
这种方式的好处是可以动态的创建Transition对象(如果你在代码中需要修改用户界面),而且创建内置Transition需要很少的参数。
1 | Transition mFadeTransition = new Fade(); |
使用Transition
Transition通常用于切换不同的View树,来响应用户操作等事件。例如:当用户输入搜索关键字并点击搜索按钮,应用切换到搜索结果布局,此时搜索界面执行淡出效果,搜索结果界面执行淡入效果。
在Activity中利用Transition切换Scene,通过调用静态方法TransitionManager.go(),并传入结束的Scene和代表动画效果的Transition实例,如下所示:
1 | TransitionManager.go(mEndingScene, mFadeTransition); |
当运行Transition实例指定的动画时,系统会将Scene Root中的View树切换成结束Scene中的View树。上一个Transition结束的Scene作为开始的Scene,如果不存在上一个Transition,用户界面的当前状态就是开始Scene。
如果没有指定Transition实例,TransitionManager会使用AutoTransition对象,它会执行大多数情况下合理的动画效果。
选择特定的Target View
默认情况下系统对开始和结束Scene中所有的View执行动画。有些时候,只希望仅仅让Scene中的一个子View运行动画。例如,系统不支持ListView对象的动画,在Transition的过程中就必须排除ListView对象。Transition框架允许只让某个特定的View运行动画。
被选定运行动画的View叫Target。你只能选择Scene中View树的子View作为Target。
Target是被保存在列表中,从Target list中删除一个或者多个Target,调用removeTarget()方法,该方法必须在执行动画之前调用。调用addTarget()方法添加View到Target list中。
定义Transition集合
在Transition系统中可以绑定一系列内置或者自定义的动画到Transition集合中。
1 | <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" |
通过在Activity中调用TransitionInflater.from()方法在代码中获取XML声明的TransitionSet对象。TransitionSet继承自Transition类,所以TransitionManager可以和使用Transition对象一样使用TransitionSet。
不使用Scene的情况下使用Transition
改变用户界面不仅仅只有通过切换View树这一种方式,还可以在当前View树中通过添加,修改,删除子View修改界面。
如果供选择的两个View树有相似的结构,则可以选择使用这种方式。不必为了用户界面的微小差别而创建和维护两个不同的资源文件,可以在资源文件中定义View树然后在代码中修改它的结构。
如果只是在一个View树改动,则不必创建Scene。而是使用delayed transition的方式在一个View树的两个状态间创建和使用Transition。Transition框架记录View树的当前状态和View的变动,在系统重绘用户界面时对View的变化执行Transition动画。
在单一的View树中创建delayed transition,遵循以下步骤:
- 当事件触发了Transition,调用TransitionManager.beginDelayedTransition()方法,传入待执行动画View的父View和Transition。系统会保存View的当前状态和属性值。
- 根据Transition改变子View。系统会记录哪些子View的哪些属性被改变了。
- 当系统根据你的变化重绘用户界面时,会在初始状态和最终状态间执行动画。
下面的代码演示了如何使用delayed transition添加一个TextView到一个View树中:
- View树对应的资源文件
1 | <!-- res/layout/activity_main.xml --> |
- 添加TextView时执行动画
1 | /* MainActivity.java */ |
定义Transition的生命周期回调
Transition的生命周期和Activity相似。它代表从调用TransitionManager.go()方法到动画运行结束过程中的状态。重要的生命状态,系统会执行TransitionListener中的回调方法。
Transition的生命周期回调是非常有用的,比如,在Scene变化过程中将某个View的属性值从开始View树中复制到结束View树中。由于结束View树直到动画结束才会被初始化,所以简单的复制值会出问题。这种情况下,可以先将值存储在一个变量中,然后当动画结束后再复制它到结束View树中。获取动画结束事件可以在Activity中实现TransitionListener.onTransitionEnd()回调方法。
参考资料