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
)。
這篇文章只是簡單解析了轉場動畫的原理,詳細的兩個頁面的跳轉將會的下一篇展開。