Android5.0之后新增了很多好看的转场动画,相比于以前的overridePendingTransition()
丰富了很多,特别新增了共享元素跳转的方式。本篇文章介绍转场动画框架的基本概念,并着手自己实现转场动画。
Scene(场景)
Scene保存了一个布局文件。我们可以通过以下方式生成一个Scene:public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
这个方法时静态的,传入一个根布局ViewGroup(作为显示场景的容器),一个layoutId(场景的显示内容),最后传入当前上下文。
源码很短,我们一起来看一下:
public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) {
SparseArray<Scene> scenes = (SparseArray<Scene>) sceneRoot.getTag(
com.android.internal.R.id.scene_layoutid_cache);
if (scenes == null) {
scenes = new SparseArray<Scene>();
sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes);
}
Scene scene = scenes.get(layoutId);
if (scene != null) {
return scene;
} else {
scene = new Scene(sceneRoot, layoutId, context);
scenes.put(layoutId, scene);
return scene;
}
}复制代码
- 根据一个固定的Tag取得保存依附于这个ViewGroup的scene集合
SparseArray<Scene> scenes
,如果是空就先new一个。 - 以要显示场景的layoutId为Key,先尝试获取这个场景,如果已经有这个layoutId对应的场景就直接返回,没有就先调用构造方法生成一个再放入进去,然后返回。
- 一个scene只能对应一个布局,scene只是简单保存了
sceneRoot, layoutId, context
的值,并没有通过layoutId来分析处理里面的View信息(也没有必要) - 可以通过
setEnterAction(Runnable action)
,setExitAction(Runnable action)
,在场景被加载和移除时回调,做相应的操作。
Transition(变换)
上面的介绍scene将一个或多个布局和一个加载这些布局的根布局建立起关系。真正的动画是由Transition实现的。
所以大致的流程是:
//为Scene创建scene root
mSceneRoot = (ViewGroup) findViewById(R.id.scene_root);
//创建 scenes
Scene mAScene = Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this);
Scene mAnotherScene = Scene.getSceneForLayout(mSceneRoot, R.layout.another_scene, this);
//代码中创建Transition
Transition mFadeTransition = new Fade();
//用TransitionManager负责场景变换
TransitionManager.go(mEndingScene, mFadeTransition);复制代码
自定义Transition
Transition是个抽象类,必须要实现以下方法:
public abstract void captureStartValues(TransitionValues transitionValues);
捕获当前场景的视图,这里会对视图树中所有的View调用,有几个View就会调用几次。public abstract void captureEndValues(TransitionValues transitionValues);
捕获目标场景的视图,这里会对视图树中所有的View调用,有几个View就会调用几次。public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,TransitionValues endValues)
(不实现方法这个就没动画效果)
从命名就可以看到captureStartValues
和captureStartValues
分别用来捕获当前场景和目标场景。
TransitionValues
有三个重要属性,对理解Transition框架的机制有很大帮助。
View view
:就是一个场景的一个View,在里面拿到View,我们可以从里面得到这个View我们所需要的属性。Map<String, Object> values
:默认为空,我们拿到属性后需要放到里面,如果这个Transition需要改变多个属性,就可以放多次进去。ArrayList<Transition> targetedTransitions
:默认为空,用来记录这个View执行了哪些Transition,我们可以在对这个View执行Transition的时候,把这个Transition存进去。
createAnimator
方法就是Transition真正的实现方法了,返回一个属性动画。
好了实战开始,我们就实现一个Transition来实现直角移动:
public class ChangeRect extends Transition {
private static final String PROPNAME_BER =
"changeposition:Rect";
// 开始的状态,这里会对视图树中所有的View调用,这里我们可以记录一下View的我们感兴趣的状态,比如这里:position
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
// 结束也会对所有的View进行调用
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
private void captureValues(TransitionValues transitionValues) {
float[] location = new float[2];
location[0] = transitionValues.view.getX();
location[1] = transitionValues.view.getY();
transitionValues.values.put(PROPNAME_BER, location);
}
//新建动画
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View view = endValues.view;
float[] startPosition = (float[]) startValues.values.get(PROPNAME_BER);
float[] endPosition = (float[]) endValues.values.get(PROPNAME_BER);
if (startPosition[0] != endPosition[0] || startPosition[1] != endPosition[1]) {
Path path=new Path();
path.moveTo(startPosition[0],startPosition[1]);
path.lineTo(endPosition[0],startPosition[1]);
path.lineTo(endPosition[0],endPosition[1]);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
animator.setDuration(getDuration());
animator.start();
return animator;
}
return null;
}
}复制代码
总结
简述下Transition框架的执行机制,我们定义了两个Scene,,当我们通过 TransitionManager.go( scene , transition)
,从Scene跳转到目标Scene的时候,会去取得scene对应布局,遍历布局中的每一个View(包括根布局和容器View),获取我们需要的属性。通过View的Id我们建立起两个布局中View的对应关系,所以最终只会在目标场景执行原场景有相同Id的View的动画(满足startValues != null && endValues!= null
)。
这篇文章只是简单解析了转场动画的原理,详细的两个页面的跳转将会的下一篇展开。