动画体系知识梳理(1) 转场动画 ContentTransition 理论篇
一、概述
在Android 5.0當中,Google基于Android 4.4中的Transition框架引入了轉場動畫,設計轉場動畫的目的,在于讓Activity之間或者Fragment之間的切換更加自然,其根本原因在于界面間切換時的動畫不再是以Activity或者Fragment的整個布局作為切換時動畫的執行單元,而是將動畫的執行單元細分到了View。目前提供的轉場動畫分為兩種:
- Content Transition:用于兩個界面之間非共享的View。
- Shared Element Transition:用于兩個界面之間需要共享的View。
二、什么是Transition
2.1 Transition的基本概念
在學習Content Transition之前,我們先對轉場動畫所依賴的Transition框架做一個簡要的介紹,這個框架是圍繞著兩個概念**Scene(場景)和Transition(變換)**來建立的,在后面我們會多次提到它:
- 場景(Scene):表示UI所對應的狀態,一般來說,會有兩個場景:起點場景和終點場景,在這兩個場景當中,UI有可能會有不同的狀態。在上圖當中,SceneA和SceneB就是兩個不同的場景,ViewA在兩個場景中對應的狀態分別為VISIBLE和INVISIBLE。
- 變換(Transition):用來定義兩個場景之間切換的規則,當場景發生發生變換時,Transition需要做的有兩點:
- 確定View在起點場景和終點場景的狀態。
- 創建View從終點場景切換到終點場景所需的Animator。
2.2 Transition的簡單例子
下面,我們通過一個簡單的例子,對上面的概念有一個直觀的感受:
public class ExampleActivity extends Activity implements View.OnClickListener {private ViewGroup mRootView;private View mRedBox, mGreenBox, mBlueBox, mBlackBox;@Overrideprotected 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);}@Overridepublic 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慢慢隱藏。
2.3 Transition小結
我們可以總結出Transition的兩個特點:
- Animator對于開發者而言是抽象的,開發者設置View的起始值和最終值,Transition會根據這兩者的差異,自動地創建切換的Animator。
- 可以隨時通過替換Transition來改變切換的規則。
三、Content Transition基本概念
3.1 舊的界面切換動畫
回憶一下,在5.0之前:
- 給Activity之間的切換添加動畫,在啟動Activity的地方加上overridePendingTransition
- 給Fragment之間的切換添加動畫,通過FragmentTransation的setCustomAnimation。
這兩種方式都有一個共同的特點,那就是它們都是將Activity所在的窗口或Fragment所對應的布局作為切換動畫的執行單元。
3.2 新的界面切換動畫
在新的切換方式當中,可以將布局中的每個View作為切換的執行單元,我們以Activity之間的切換為例。
3.2.1 啟動BActivity
在AActivity啟動中BActivity,這時候就會涉及到四種Scene和兩種Transition:
- AActivity's Exit Transition:它定義了AActivity中的元素如何從VISIBLE(起點場景)變為INVISIBLE(終點場景)。
- BActivity's Enter Transition:它定義了BActivity中的元素如果從INVISIBLE(起點場景)變為VISIBLE(終點場景)。
3.2.1.1 確定需要執行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來改變這個集合。
3.2.1.2 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。
3.2.1.3 Enter Transition的執行過程。
BActivity的Enter Transition和AActvity的Exit Transition類似,只不過第三步操作是從INVISIBLE到VISIBLE。
3.2.2 從BActivity返回
而當我們從BActivity返回到AActivity,那么就會涉及到下面四種Scene和兩種Transition:
- BActivity's Return Transition
- AActivity's Reenter Transition
其原理和上面是相同的,就不多介紹了。
3.2.3 小結
無論是AActivity啟動BActivity,還是BActivity返回到AActivity,當View的可見性不斷切換的時候,系統能保證根據狀態信息來創建所需的動畫。很顯然,所有的Content transition對象都需要能夠捕捉并記錄View的起始狀態和終點狀態,幸運的是,抽象類Visiblity已經幫我們做了,我們只需要實現onAppear和onDisappear方法,在里面創建一個Animator來定義進入和退出場景的View的動畫,系統默認提供了三種Transition - Fade、Slide、Explode,下面我們在分析Fade源碼的時候,會詳細解釋這一過程。
3.3 Content Transition和Shared Element Transition
在上面的討論當中,我們是從切換的角度來考慮的,而如果我們從Transition的角度來看,那么每個Transition又可以細分為兩類:
- content transitions:定義了Activity非共享View進入和退出場景的方式。
- shared element transitions:定義了Acitivity共享View進入和退出場景的方法。
3.4 例子
下面,我們以一個視頻來解釋一下上面談到的四個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,這一節,我們一起來分析一下它們的實現方式:
4.1 Fade
4.1.1 captureXXX函數
首先,我們看一下它獲取起點和終點屬性的函數:
- public void captureStartValues(TransitionValues transitionValues)
- public void captureEndValues(TransitionValues transitionValues)
Fade只重寫了captureStartValues,在這里面,它把View當前的translationAlpha值保存起來,這個值表示的是在Transition開始之前View的translationAlpha的值:
@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, transitionValues.view.getTransitionAlpha());} 復制代碼4.1.2 onAppear和onDisappear
在上面的分析當中,我們提到過,當View的可見性從INVISIBLE變為VISIBLE時會調用Transition中的Animator來執行這一變換的過程,例如從AActivity跳轉到BActivity,那么BActivity中的View就會調用onAppear所返回的Animator:
@Overridepublic 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() {@Overridepublic void onTransitionEnd(Transition transition) {view.setTransitionAlpha(1);}});return anim;} 復制代碼從上面可以看出,它返回的是一個ObjectAnimator,這個Animator會把View的translationAlpha從startAlpha變為1,這也就是一個漸漸顯示的過程。 再看一下onDisappear函數,它就是onAppear的反向過程:
@Overridepublic Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,TransitionValues endValues) {float startAlpha = getStartAlpha(startValues, 1);return createAnimation(view, startAlpha, 0);} 復制代碼4.2 Slide
下面,我們來看一下另一種Transition - Slide的實現原理,和上面類似,我們先看一下captureXXX方都做了什么:
4.2.1 captureXXX
@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);captureValues(transitionValues);}@Overridepublic 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); } 復制代碼4.2.2 onAppear和onDisappear
@Overridepublic 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() {@Overridepublic float getGoneY(ViewGroup sceneRoot, View view, float fraction) {return view.getTranslationY() + sceneRoot.getHeight() * fraction;}}; 復制代碼用一張圖解解釋一下上面的坐標:
所以當我們采用這個Transition的時候,就可以看到它從屏幕的底端滑上來。 而onDisappear則也是一個反向的過程: @Overridepublic 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);} 復制代碼4.3 小結
通過分析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
總結
以上是生活随笔為你收集整理的动画体系知识梳理(1) 转场动画 ContentTransition 理论篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Dagger2 知识梳理(1) Da
- 下一篇: 建立项目的webpack简单配置