之前在github上寻找Activity转场时偶然看到一个令人惊艳的项目:
Material-Animations(另有网友贡献了中文版本)
其中的效果有很多,我们着重看看其中比较常见的效果。
如:同一个Layout内视图变化时的动画
之所以说惊艳,主要是因为像这么复杂的动画效果,居然通篇不见Animation,只用了几行代码就搞定,颇有一种四两拨千斤的感觉。
其实现方式就是今天的主角:Transition 。本文重说概念,先简单介绍Transition的使用,再尝试从源码角度去分析,一探究竟。
Android Master Transition 源代码目录- master分支
Android Master Transition 源代码目录- 4.3KitKat分支
demo的效果如果自己使用Animation实现的话,代码会极其冗杂,需要计算四个View在切换时的最终坐标,推算出其动画过程。(更不要说图中的动画还有旋转等。。)
而这些在Transition的面前,需要我们做的事情就少得多了:
ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.scene_root);
(...)
scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene1, this);
scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene2, this);
scene3 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene3, this);
scene4 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene4, this);
(...)
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
TransitionManager.go(scene1, new ChangeBounds());/*ChangeBound即为Transition一个子类*/
break;
case R.id.button2:
TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
break;
case R.id.button3:
TransitionManager.go(scene3, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
break;
case R.id.button4:
TransitionManager.go(scene4, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
break;
1n }
}
非常简短,看上去简单设置了几个变换前后的layout就完成了任务。
事实上,Transition Framework提供的api远不止例子中的这几个,还有多种变形。但是只要我们厘清其核心的几个类,必能以不变应万变。
在这里首先向大家拿出三个核心概念。
概念
[1].scene root
其实就是一个ViewGroup,transition将会作用的主要布局,栗子中是一个简单的FrameLayout。
[2].scenes
记录了scene root 的某一个状态 ,可以使用scene root 和某一个具体的layoutId来定义。
可以看做是ViewGroup的一个wrapper(封装)
[3].Transition
最核心的部分,它会比对布局调整前后的不同,并且创建出合适的Animation或者Animation组合
可以先看一眼这三者的关系:
Scene 使用layout来定义,而TransitionMananger持有了starting scene 、ending scene以及Transition,并且作为入口(TransitionManange #go),托管了变换的过程。
过程
整个变换过程总结起来如下:
1. 记录 Scene中Scene Root 的初始状态
2. 应用Ending Layout
3. 记录Scene中Scene Root在第二步之后的状态
4. 根据1、3步获取的结果,设置Animation ,并且运行之
这个过程基本上涵括在了
//TranstionManager
private static void changeScene(Scene scene, Transition transition) {
final ViewGroup sceneRoot = scene.getSceneRoot();
if (!sPendingTransitions.contains(sceneRoot)) {
sPendingTransitions.add(sceneRoot);
Transition transitionClone = null;
if (transition != null) {
transitionClone = transition.clone();
transitionClone.setSceneRoot(sceneRoot);
}
Scene oldScene = Scene.getCurrentScene(sceneRoot);
if (oldScene != null && transitionClone != null &&
oldScene.isCreatedFromLayoutResource()) {
transitionClone.setCanRemoveViews(true);
}
/**1. 记录Starting Scene 的状态**/
sceneChangeSetup(sceneRoot, transitionClone);
/**2. 应用Ending Layout**/
scene.enter();
/** 3. 记录Scene中Scene Root在第二步之后的状态
4. 根据1、3步获取的结果,设置Animation ,并且运行 **/
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
1 .记录Starting Scene 的状态
//TransitionMananger.java
/**
* @param sceneRoot 从StartingScene中获取,ViewGroup
* @param transition 设置的Transition
*/
private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
// Capture current values
ArrayList runningTransitions = getRunningTransitions().get(sceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.pause(sceneRoot);
}
}
if (transition != null) {
//Transition 负责捕获、记录sceneRoot的起始状态
transition.captureValues(sceneRoot, true);
}
// Notify previous scene that it is being exited
Scene previousScene = Scene.getCurrentScene(sceneRoot);
if (previousScene != null) {
previousScene.exit();
}
} // Capture current values
ArrayList runningTransitions = getRunningTransitions().get(sceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.pause(sceneRoot);
}
}
if (transition != null) {
transition.captureValues(sceneRoot, true);
}
// Notify previous scene that it is being exited
Scene previousScene = Scene.getCurrentScene(sceneRoot);
if (previousScene != null) {
previousScene.exit();
}
2. 应用Ending Layout
//Scene.java
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
最好理解的一部分,就是去应用EndingScene。
3. 记录Scene中Scene Root在第二步之后的状态
private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
final Transition transition) {
if (transition != null && sceneRoot != null) {
MultiListener listener = new MultiListener(transition, sceneRoot);
sceneRoot.addOnAttachStateChangeListener(listener);
sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
}
}
view的属性变化自然不会立即变化,而是要等下一次View的measure和layout完成之后。因此第三步的真正实现还是在view的onPreDraw回调中实现的。
public boolean onPreDraw() {
removeListeners();
// Don't start the transition if it's no longer pending.
if (!sPendingTransitions.remove(mSceneRoot)) {
return true;
}
(...)
/** 记录已经measured和layout的SceneRoot属性**/
mTransition.captureValues(mSceneRoot, false);
if (previousRunningTransitions != null) {
for (Transition runningTransition : previousRunningTransitions) {
runningTransition.resume(mSceneRoot);
}
}
/**第四步的实现**/
mTransition.playTransition(mSceneRoot);
return true;
}
4. 根据1、3步获取的结果,设置Animation ,并且运行之
1、3 步分别捕获了Scene Root的前后状态,接下来根据这里计算出Animation。
也是自定义Transition最关键的部分,将在后面的系列中为大家解读。
对于另外一个api,Transition#beginDelayedTransition其内容几乎和上文中的完全相同,区别在于:上面中第2步其实是由我们自己来实现。
看下这个例子:
而其源码:
TransitionManager#beginDelayedTransition
public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
if (Transition.DBG) {
Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
sceneRoot + ", " + transition);
}
sPendingTransitions.add(sceneRoot);
if (transition == null) {
transition = sDefaultTransition;
}
final Transition transitionClone = transition.clone();
sceneChangeSetup(sceneRoot, transitionClone);
Scene.setCurrentScene(sceneRoot, null); //这里不同
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
demo的实现:
TransitionManager.beginDelayedTransition(sceneRoot);
ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
params.width = 200;
greenIconView.setLayoutParams(params);
总结
这里,我们大体对Transition的实现有了一个大体概念,也知晓了其大致的实现思路。其实TransitionManager 的实现比较简单,其结构也很清晰。因此,对于想探究Android源码的同学来说,是一个不可多得的例子。
Transition中还有另外几个有意思的部分: 如Transition生成动画的具体过程,Activity 和Fragment切换时应用Transition等就是我们后面要研究的重点了。
本文的另一个启示是:Transition并不是独立于Animation Framework之外的一个独立的体系,它可以看做是一个对于Animation的封装。对于广大的Android APP开发工程师而言,可能我们平日里接触到都是FrameWork层,但是即使是在FrameWork层中,也是有很多层级关系的。这对于我们“应该”做什么,“能做”什么有很好的指导 作用。