在Android 5.0
當中,Google
基於Android 4.4
中的Transition
框架引入了轉場動畫,設計轉場動畫的目的,在於讓Activity
之間或者Fragment
之間的切換更加天然,其根本緣由在於界面間切換時的動畫再也不是以Activity
或者Fragment
的整個佈局做爲切換時動畫的執行單元,而是將動畫的執行單元細分到了View
。目前提供的轉場動畫分爲兩種:html
Content Transition
:用於兩個界面之間非共享的View
。Shared Element Transition
:用於兩個界面之間須要共享的View
。Transition
Transition
的基本概念在學習Content Transition
以前,咱們先對轉場動畫所依賴的Transition
框架作一個簡要的介紹,這個框架是圍繞着兩個概念**Scene
(場景)和Transition
(變換)**來創建的,在後面咱們會屢次提到它: android
Scene
):表示UI
所對應的狀態,通常來講,會有兩個場景:起點場景和終點場景,在這兩個場景當中,UI
有可能會有不一樣的狀態。在上圖當中,SceneA
和SceneB
就是兩個不一樣的場景,ViewA
在兩個場景中對應的狀態分別爲VISIBLE
和INVISIBLE
。Transition
):用來定義兩個場景之間切換的規則,當場景發生發生變換時,Transition
須要作的有兩點:View
在起點場景和終點場景的狀態。View
從終點場景切換到終點場景所需的Animator
。Transition
的簡單例子下面,咱們經過一個簡單的例子,對上面的概念有一個直觀的感覺:git
public class ExampleActivity extends Activity implements View.OnClickListener {
private ViewGroup mRootView;
private View mRedBox, mGreenBox, mBlueBox, mBlackBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootView = (ViewGroup) findViewById(R.id.layout_root_view);
mRootView.setOnClickListener(this);
mRedBox = findViewById(R.id.red_box);
mGreenBox = findViewById(R.id.green_box);
mBlueBox = findViewById(R.id.blue_box);
mBlackBox = findViewById(R.id.black_box);
}
@Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(mRootView, new Fade());
toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);
}
private static void toggleVisibility(View... views) {
for (View view : views) {
boolean isVisible = view.getVisibility() == View.VISIBLE;
view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);
}
}
}
複製代碼
beginDelayedTranstion
傳入場景對應佈局的根節點(mRootView
)以及場景變換的規則(Fade
),此時系統理解調用Transition
的captureStartValues
方法,來肯定場景當中全部子View
的visibility
。beginDeleyedTransition
返回後,咱們將子View
設置爲不可見。Transtion
的captureEndValues()
方法獲取場景當中全部子View
的可見性。View
是VISIBLE
的,而在終點場景中它變爲了INVISIBLE
,那麼Fade Transition
就會根據這些信息建立並返回AnimatorSet
,用它來將那些發生變化的View
的alpha
值漸變爲0
,而不是直接設爲不可見。Animator
,使得這些View
慢慢隱藏。Transition
小結咱們能夠總結出Transition
的兩個特色:github
Animator
對於開發者而言是抽象的,開發者設置View
的起始值和最終值,Transition
會根據這二者的差別,自動地建立切換的Animator
。Transition
來改變切換的規則。Content Transition
基本概念回憶一下,在5.0
以前:bash
Activity
之間的切換添加動畫,在啓動Activity
的地方加上overridePendingTransition
Fragment
之間的切換添加動畫,經過FragmentTransation
的setCustomAnimation
。這兩種方式都有一個共同的特色,那就是它們都是將Activity
所在的窗口或Fragment
所對應的佈局做爲切換動畫的執行單元。app
在新的切換方式當中,能夠將佈局中的每一個View
做爲切換的執行單元,咱們以Activity
之間的切換爲例。框架
BActivity
在AActivity
啓動中BActivity
,這時候就會涉及到四種Scene
和兩種Transition
: ide
AActivity's Exit Transition
:它定義了AActivity
中的元素如何從VISIBLE
(起點場景)變爲INVISIBLE
(終點場景)。BActivity's Enter Transition
:它定義了BActivity
中的元素若是從INVISIBLE
(起點場景)變爲VISIBLE
(終點場景)。Transition
的View
整個Transition
的第一步,就是先要肯定當前界面中須要執行Transition
的動畫切換單元,這一過程是經過對整個View
樹進行遞歸調用獲得的,而遞歸的邏輯在ViewGroup
當中:函數
public void captureTransitioningViews(List<View> transitioningViews) {
if (getVisibility() != View.VISIBLE) {
return;
}
if (isTransitionGroup()) {
transitioningViews.add(this);
} else {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.captureTransitioningViews(transitioningViews);
}
}
}
複製代碼
而在View
中,該方法爲:源碼分析
public void captureTransitioningViews(List<View> transitioningViews) {
if (getVisibility() == View.VISIBLE) {
transitioningViews.add(this);
}
}
複製代碼
因而可知,全部須要變換的ViewGroup/View
都保存在transitioningViews
當中,關於這個集合的構成依據如下三點:
isTransitionGroup()
標誌位爲true
,那麼把它和它的全部子節點當成一個變換單元加入到集合當中。View
樹的全部葉子節點都加入到集合當中。其中isTransitionGroup()
的值咱們能夠經過setTransitionGroup(boolean flag)
來改變,若是在場景當中用到了WebView
,而咱們但願將它做爲一個總體進行變換,那麼應當加上這個標誌位。 除了系統默認的遍歷,咱們還能夠經過Transition
的added
和excluded
來改變這個集合。
Exit Transition
的執行過程下面,咱們以AActivity
的Exit Transition
爲例,描述一下它整個的執行過程:
AActivity
的View
樹,並決定在exit transition
運行時須要變換的View
,把它們放在集合當中,也就是咱們在3.2.1.1
中所說的transitionViews
。AActivity
的Exit Transition
獲取集合中View
的起始狀態,調用的是captureStartValues
方法。View
設爲INVISIBLE
。Exit Transition
獲取集合中View
的終點狀態,調用的是captureEndValues
方法。Exit Transition
根據第二步中的起始狀態和終點狀態,建立一個Animator
,並執行這個Animator
,因爲是從VISIBLE
變爲INVISIBLE
,所以,是經過onDisappear
方法獲得Animator
。Enter Transition
的執行過程。BActivity
的Enter Transition
和AActvity
的Exit Transition
相似,只不過第三步操做是從INVISIBLE
到VISIBLE
。
BActivity
返回而當咱們從BActivity
返回到AActivity
,那麼就會涉及到下面四種Scene
和兩種Transition
:
BActivity's Return Transition
AActivity's Reenter Transition
其原理和上面是相同的,就很少介紹了。
不管是AActivity
啓動BActivity
,仍是BActivity
返回到AActivity
,當View
的可見性不斷切換的時候,系統能保證根據狀態信息來建立所需的動畫。很顯然,全部的Content transition
對象都須要可以捕捉並記錄View
的起始狀態和終點狀態,幸運的是,抽象類Visiblity
已經幫咱們作了,咱們只須要實現onAppear
和onDisappear
方法,在裏面建立一個Animator
來定義進入和退出場景的View
的動畫,系統默認提供了三種Transition
- Fade、Slide、Explode
,下面咱們在分析Fade
源碼的時候,會詳細解釋這一過程。
Content Transition
和Shared Element Transition
在上面的討論當中,咱們是從切換的角度來考慮的,而若是咱們從Transition
的角度來看,那麼每一個Transition
又能夠細分爲兩類:
content transitions
:定義了Activity
非共享View
進入和退出場景的方式。shared element transitions
:定義了Acitivity
共享View
進入和退出場景的方法。下面,咱們以一個視頻來解釋一下上面談到的四個Transition
:
AActivity
,詳情頁稱爲
BActivity
,此時,對應於上面提到的四種
Transition
:
AActivity's Exit Transition
爲null
AActivity's Reenter Transition
爲null
BActivity's Enter Transition
則分爲三個部分:Slide-in
動畫。BActivity's Exit Transition
:Slide(TOP)
的方式,而下半部分採用Slide(BOTTOM)
的方式。系統默認自帶了三種Transition
,Fade、Slide、Explode
,這一節,咱們一塊兒來分析一下它們的實現方式:
Fade
captureXXX
函數首先,咱們看一下它獲取起點和終點屬性的函數:
public void captureStartValues(TransitionValues transitionValues)
public void captureEndValues(TransitionValues transitionValues)
Fade
只重寫了captureStartValues
,在這裏面,它把View
當前的translationAlpha
值保存起來,這個值表示的是在Transition
開始以前View
的translationAlpha
的值:
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, transitionValues.view.getTransitionAlpha());
}
複製代碼
onAppear
和onDisappear
在上面的分析當中,咱們提到過,當View
的可見性從INVISIBLE
變爲VISIBLE
時會調用Transition
中的Animator
來執行這一變換的過程,例如從AActivity
跳轉到BActivity
,那麼BActivity
中的View
就會調用onAppear
所返回的Animator
:
@Override
public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {
float startAlpha = getStartAlpha(startValues, 0);
if (startAlpha == 1) {
startAlpha = 0;
}
return createAnimation(view, startAlpha, 1);
}
複製代碼
這裏首先會經過getStartAlpha
去獲取起始的transitionAlpha
值,它是把以前保存在PROPNAME_TRANSITION_ALPHA
中的值取出來:
private static float getStartAlpha(TransitionValues startValues, float fallbackValue) {
float startAlpha = fallbackValue;
if (startValues != null) {
Float startAlphaFloat = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA);
if (startAlphaFloat != null) {
startAlpha = startAlphaFloat;
}
}
return startAlpha;
}
複製代碼
下面,咱們再回到onAppear
函數當中,看一下Animator
的建立過程:
private Animator createAnimation(final View view, float startAlpha, final float endAlpha) {
if (startAlpha == endAlpha) {
return null;
}
view.setTransitionAlpha(startAlpha);
final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);
final FadeAnimatorListener listener = new FadeAnimatorListener(view);
anim.addListener(listener);
addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
view.setTransitionAlpha(1);
}
});
return anim;
}
複製代碼
從上面能夠看出,它返回的是一個ObjectAnimator
,這個Animator
會把View
的translationAlpha
從startAlpha
變爲1
,這也就是一個漸漸顯示的過程。 再看一下onDisappear
函數,它就是onAppear
的反向過程:
@Override
public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
TransitionValues endValues) {
float startAlpha = getStartAlpha(startValues, 1);
return createAnimation(view, startAlpha, 0);
}
複製代碼
Slide
下面,咱們來看一下另外一種Transition
- Slide
的實現原理,和上面相似,咱們先看一下captureXXX
方都作了什麼:
captureXXX
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
super.captureEndValues(transitionValues);
captureValues(transitionValues);
}
複製代碼
對於起點和終點值的獲取都是調用了下面這個函數,它保存的是View
在窗口中的位置:
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
int[] position = new int[2];
view.getLocationOnScreen(position);
transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
}
複製代碼
onAppear
和onDisappear
@Override
public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {
if (endValues == null) {
return null;
}
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
//終點值是肯定的
float endX = view.getTranslationX();
float endY = view.getTranslationY();
//起點值則須要根據所選的模式來肯定
float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);
float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);
//根據起點值、終點值、View所處窗口的位置,來獲得一個`Animator`
return TranslationAnimationCreator.createAnimation(view, endValues, position[0], position[1], startX, startY, endX, endY, sDecelerate, this);
}
複製代碼
這裏面,最關鍵的是mSlideCalculator
,默認狀況下爲:
private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
@Override
public float getGoneY(ViewGroup sceneRoot, View view, float fraction) {
return view.getTranslationY() + sceneRoot.getHeight() * fraction;
}
};
複製代碼
用一張圖解解釋一下上面的座標:
因此當咱們採用這個Transition
的時候,就能夠看到它從屏幕的底端滑上來。 而
onDisappear
則也是一個反向的過程:
@Override
public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {
if (startValues == null) {
return null;
}
int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
//這裏的起始值和終點值正好是和onAppear相反的.
float startX = view.getTranslationX();
float startY = view.getTranslationY();
float endX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);
float endY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);
return TranslationAnimationCreator.createAnimation(view, startValues, position[0], position[1], startX, startY, endX, endY, sAccelerate, this);
}
複製代碼
經過分析Fade
和Slide
的源碼,它們的主要思想就是:
capturexxx
方法中,把屬性保存在TranslationValues
中,這裏,必定要記得調用對應的super
方法讓系統保存一些默認的狀態。onAppear
和onDisappear
中,根據起點和終點和終點的TranslationValues
,構造一個改變View
屬性的Animator
,同時在動畫結束以後,還原它的屬性。這一篇咱們分析了Content Transition
的設計思想和原理,下一篇文章咱們將着重討論如何經過代碼來實現上面的效果。
1.Getting Started with Activity & Fragment Transitions (part 1) 2.Content Transitions In-Depth (part 2) 3.Material-Animations