动画体系知识梳理(1) 转场动画 ContentTransition 理论篇
一、概述
在Android 5.0當(dāng)中,Google基于Android 4.4中的Transition框架引入了轉(zhuǎn)場(chǎng)動(dòng)畫,設(shè)計(jì)轉(zhuǎn)場(chǎng)動(dòng)畫的目的,在于讓Activity之間或者Fragment之間的切換更加自然,其根本原因在于界面間切換時(shí)的動(dòng)畫不再是以Activity或者Fragment的整個(gè)布局作為切換時(shí)動(dòng)畫的執(zhí)行單元,而是將動(dòng)畫的執(zhí)行單元細(xì)分到了View。目前提供的轉(zhuǎn)場(chǎng)動(dòng)畫分為兩種:
- Content Transition:用于兩個(gè)界面之間非共享的View。
- Shared Element Transition:用于兩個(gè)界面之間需要共享的View。
二、什么是Transition
2.1 Transition的基本概念
在學(xué)習(xí)Content Transition之前,我們先對(duì)轉(zhuǎn)場(chǎng)動(dòng)畫所依賴的Transition框架做一個(gè)簡(jiǎn)要的介紹,這個(gè)框架是圍繞著兩個(gè)概念**Scene(場(chǎng)景)和Transition(變換)**來建立的,在后面我們會(huì)多次提到它:
- 場(chǎng)景(Scene):表示UI所對(duì)應(yīng)的狀態(tài),一般來說,會(huì)有兩個(gè)場(chǎng)景:起點(diǎn)場(chǎng)景和終點(diǎn)場(chǎng)景,在這兩個(gè)場(chǎng)景當(dāng)中,UI有可能會(huì)有不同的狀態(tài)。在上圖當(dāng)中,SceneA和SceneB就是兩個(gè)不同的場(chǎng)景,ViewA在兩個(gè)場(chǎng)景中對(duì)應(yīng)的狀態(tài)分別為VISIBLE和INVISIBLE。
- 變換(Transition):用來定義兩個(gè)場(chǎng)景之間切換的規(guī)則,當(dāng)場(chǎng)景發(fā)生發(fā)生變換時(shí),Transition需要做的有兩點(diǎn):
- 確定View在起點(diǎn)場(chǎng)景和終點(diǎn)場(chǎng)景的狀態(tài)。
- 創(chuàng)建View從終點(diǎn)場(chǎng)景切換到終點(diǎn)場(chǎng)景所需的Animator。
2.2 Transition的簡(jiǎn)單例子
下面,我們通過一個(gè)簡(jiǎn)單的例子,對(duì)上面的概念有一個(gè)直觀的感受:
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);}} } 復(fù)制代碼- 第一步:通過beginDelayedTranstion傳入場(chǎng)景對(duì)應(yīng)布局的根節(jié)點(diǎn)(mRootView)以及場(chǎng)景變換的規(guī)則(Fade),此時(shí)系統(tǒng)理解調(diào)用Transition的captureStartValues方法,來確定場(chǎng)景當(dāng)中所有子View的visibility。
- 第二步:當(dāng)beginDeleyedTransition返回后,我們將子View設(shè)置為不可見。
- 第三步:在下一幀,系統(tǒng)調(diào)用Transtion的captureEndValues()方法獲取場(chǎng)景當(dāng)中所有子View的可見性。
- 第四步:這時(shí)候系統(tǒng)發(fā)現(xiàn)在起始場(chǎng)景中View是VISIBLE的,而在終點(diǎn)場(chǎng)景中它變?yōu)榱薎NVISIBLE,那么Fade Transition就會(huì)根據(jù)這些信息創(chuàng)建并返回AnimatorSet,用它來將那些發(fā)生變化的View的alpha值漸變?yōu)?,而不是直接設(shè)為不可見。
- 第五步:系統(tǒng)啟動(dòng)這個(gè)Animator,使得這些View慢慢隱藏。
2.3 Transition小結(jié)
我們可以總結(jié)出Transition的兩個(gè)特點(diǎn):
- Animator對(duì)于開發(fā)者而言是抽象的,開發(fā)者設(shè)置View的起始值和最終值,Transition會(huì)根據(jù)這兩者的差異,自動(dòng)地創(chuàng)建切換的Animator。
- 可以隨時(shí)通過替換Transition來改變切換的規(guī)則。
三、Content Transition基本概念
3.1 舊的界面切換動(dòng)畫
回憶一下,在5.0之前:
- 給Activity之間的切換添加動(dòng)畫,在啟動(dòng)Activity的地方加上overridePendingTransition
- 給Fragment之間的切換添加動(dòng)畫,通過FragmentTransation的setCustomAnimation。
這兩種方式都有一個(gè)共同的特點(diǎn),那就是它們都是將Activity所在的窗口或Fragment所對(duì)應(yīng)的布局作為切換動(dòng)畫的執(zhí)行單元。
3.2 新的界面切換動(dòng)畫
在新的切換方式當(dāng)中,可以將布局中的每個(gè)View作為切換的執(zhí)行單元,我們以Activity之間的切換為例。
3.2.1 啟動(dòng)BActivity
在AActivity啟動(dòng)中BActivity,這時(shí)候就會(huì)涉及到四種Scene和兩種Transition:
- AActivity's Exit Transition:它定義了AActivity中的元素如何從VISIBLE(起點(diǎn)場(chǎng)景)變?yōu)镮NVISIBLE(終點(diǎn)場(chǎng)景)。
- BActivity's Enter Transition:它定義了BActivity中的元素如果從INVISIBLE(起點(diǎn)場(chǎng)景)變?yōu)閂ISIBLE(終點(diǎn)場(chǎng)景)。
3.2.1.1 確定需要執(zhí)行Transition的View
整個(gè)Transition的第一步,就是先要確定當(dāng)前界面中需要執(zhí)行Transition的動(dòng)畫切換單元,這一過程是通過對(duì)整個(gè)View樹進(jìn)行遞歸調(diào)用得到的,而遞歸的邏輯在ViewGroup當(dāng)中:
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);}} } 復(fù)制代碼而在View中,該方法為:
public void captureTransitioningViews(List<View> transitioningViews) {if (getVisibility() == View.VISIBLE) {transitioningViews.add(this);} } 復(fù)制代碼由此可見,所有需要變換的ViewGroup/View都保存在transitioningViews當(dāng)中,關(guān)于這個(gè)集合的構(gòu)成依據(jù)以下三點(diǎn):
- 節(jié)點(diǎn)不可見,那么它以及它的所有子節(jié)點(diǎn)都不加入集合。
- 節(jié)點(diǎn)的isTransitionGroup()標(biāo)志位為true,那么把它和它的所有子節(jié)點(diǎn)當(dāng)成一個(gè)變換單元加入到集合當(dāng)中。
- 除了以上兩種情況,那么View樹的所有葉子節(jié)點(diǎn)都加入到集合當(dāng)中。
其中isTransitionGroup()的值我們可以通過setTransitionGroup(boolean flag)來改變,如果在場(chǎng)景當(dāng)中用到了WebView,而我們希望將它作為一個(gè)整體進(jìn)行變換,那么應(yīng)當(dāng)加上這個(gè)標(biāo)志位。 除了系統(tǒng)默認(rèn)的遍歷,我們還可以通過Transition的added和excluded來改變這個(gè)集合。
3.2.1.2 Exit Transition的執(zhí)行過程
下面,我們以AActivity的Exit Transition為例,描述一下它整個(gè)的執(zhí)行過程:
- 第一步:系統(tǒng)遍歷AActivity的View樹,并決定在exit transition運(yùn)行時(shí)需要變換的View,把它們放在集合當(dāng)中,也就是我們?cè)?.2.1.1中所說的transitionViews。
- 第二步:AActivity的Exit Transition獲取集合中View的起始狀態(tài),調(diào)用的是captureStartValues方法。
- 第三步:將集合中的View設(shè)為INVISIBLE。
- 第四步:在下一幀時(shí),Exit Transition獲取集合中View的終點(diǎn)狀態(tài),調(diào)用的是captureEndValues方法。
- 第五步:Exit Transition根據(jù)第二步中的起始狀態(tài)和終點(diǎn)狀態(tài),創(chuàng)建一個(gè)Animator,并執(zhí)行這個(gè)Animator,由于是從VISIBLE變?yōu)镮NVISIBLE,因此,是通過onDisappear方法得到Animator。
3.2.1.3 Enter Transition的執(zhí)行過程。
BActivity的Enter Transition和AActvity的Exit Transition類似,只不過第三步操作是從INVISIBLE到VISIBLE。
3.2.2 從BActivity返回
而當(dāng)我們從BActivity返回到AActivity,那么就會(huì)涉及到下面四種Scene和兩種Transition:
- BActivity's Return Transition
- AActivity's Reenter Transition
其原理和上面是相同的,就不多介紹了。
3.2.3 小結(jié)
無論是AActivity啟動(dòng)BActivity,還是BActivity返回到AActivity,當(dāng)View的可見性不斷切換的時(shí)候,系統(tǒng)能保證根據(jù)狀態(tài)信息來創(chuàng)建所需的動(dòng)畫。很顯然,所有的Content transition對(duì)象都需要能夠捕捉并記錄View的起始狀態(tài)和終點(diǎn)狀態(tài),幸運(yùn)的是,抽象類Visiblity已經(jīng)幫我們做了,我們只需要實(shí)現(xiàn)onAppear和onDisappear方法,在里面創(chuàng)建一個(gè)Animator來定義進(jìn)入和退出場(chǎng)景的View的動(dòng)畫,系統(tǒng)默認(rèn)提供了三種Transition - Fade、Slide、Explode,下面我們?cè)诜治鯢ade源碼的時(shí)候,會(huì)詳細(xì)解釋這一過程。
3.3 Content Transition和Shared Element Transition
在上面的討論當(dāng)中,我們是從切換的角度來考慮的,而如果我們從Transition的角度來看,那么每個(gè)Transition又可以細(xì)分為兩類:
- content transitions:定義了Activity非共享View進(jìn)入和退出場(chǎng)景的方式。
- shared element transitions:定義了Acitivity共享View進(jìn)入和退出場(chǎng)景的方法。
3.4 例子
下面,我們以一個(gè)視頻來解釋一下上面談到的四個(gè)Transition:
在這個(gè)視頻當(dāng)中,我們將列表頁(yè)稱為AActivity,詳情頁(yè)稱為BActivity,此時(shí),對(duì)應(yīng)于上面提到的四種Transition:- AActivity's Exit Transition為null
- AActivity's Reenter Transition為null
- BActivity's Enter Transition則分為三個(gè)部分:
- 封面從小的圓形漸漸變成大的方形
- 播放圖標(biāo)的半徑漸漸變大
- 底下的列表采用了自定義的Slide-in動(dòng)畫。
- BActivity's Exit Transition:
- 上半部分采用了Slide(TOP)的方式,而下半部分采用Slide(BOTTOM)的方式。
四、源碼分析
系統(tǒng)默認(rèn)自帶了三種Transition,Fade、Slide、Explode,這一節(jié),我們一起來分析一下它們的實(shí)現(xiàn)方式:
4.1 Fade
4.1.1 captureXXX函數(shù)
首先,我們看一下它獲取起點(diǎn)和終點(diǎn)屬性的函數(shù):
- public void captureStartValues(TransitionValues transitionValues)
- public void captureEndValues(TransitionValues transitionValues)
Fade只重寫了captureStartValues,在這里面,它把View當(dāng)前的translationAlpha值保存起來,這個(gè)值表示的是在Transition開始之前View的translationAlpha的值:
@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);transitionValues.values.put(PROPNAME_TRANSITION_ALPHA, transitionValues.view.getTransitionAlpha());} 復(fù)制代碼4.1.2 onAppear和onDisappear
在上面的分析當(dāng)中,我們提到過,當(dāng)View的可見性從INVISIBLE變?yōu)閂ISIBLE時(shí)會(huì)調(diào)用Transition中的Animator來執(zhí)行這一變換的過程,例如從AActivity跳轉(zhuǎn)到BActivity,那么BActivity中的View就會(huì)調(diào)用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);} 復(fù)制代碼這里首先會(huì)通過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;} 復(fù)制代碼下面,我們?cè)倩氐給nAppear函數(shù)當(dāng)中,看一下Animator的創(chuàng)建過程:
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;} 復(fù)制代碼從上面可以看出,它返回的是一個(gè)ObjectAnimator,這個(gè)Animator會(huì)把View的translationAlpha從startAlpha變?yōu)?,這也就是一個(gè)漸漸顯示的過程。 再看一下onDisappear函數(shù),它就是onAppear的反向過程:
@Overridepublic Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,TransitionValues endValues) {float startAlpha = getStartAlpha(startValues, 1);return createAnimation(view, startAlpha, 0);} 復(fù)制代碼4.2 Slide
下面,我們來看一下另一種Transition - Slide的實(shí)現(xiàn)原理,和上面類似,我們先看一下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);} 復(fù)制代碼對(duì)于起點(diǎn)和終點(diǎn)值的獲取都是調(diào)用了下面這個(gè)函數(shù),它保存的是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); } 復(fù)制代碼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);//終點(diǎn)值是確定的float endX = view.getTranslationX();float endY = view.getTranslationY();//起點(diǎn)值則需要根據(jù)所選的模式來確定float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction);float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction);//根據(jù)起點(diǎn)值、終點(diǎn)值、View所處窗口的位置,來得到一個(gè)`Animator`return TranslationAnimationCreator.createAnimation(view, endValues, position[0], position[1], startX, startY, endX, endY, sDecelerate, this);} 復(fù)制代碼這里面,最關(guān)鍵的是mSlideCalculator,默認(rèn)情況下為:
private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {@Overridepublic float getGoneY(ViewGroup sceneRoot, View view, float fraction) {return view.getTranslationY() + sceneRoot.getHeight() * fraction;}}; 復(fù)制代碼用一張圖解解釋一下上面的坐標(biāo):
所以當(dāng)我們采用這個(gè)Transition的時(shí)候,就可以看到它從屏幕的底端滑上來。 而onDisappear則也是一個(gè)反向的過程: @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);//這里的起始值和終點(diǎn)值正好是和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);} 復(fù)制代碼4.3 小結(jié)
通過分析Fade和Slide的源碼,它們的主要思想就是:
- 在capturexxx方法中,把屬性保存在TranslationValues中,這里,一定要記得調(diào)用對(duì)應(yīng)的super方法讓系統(tǒng)保存一些默認(rèn)的狀態(tài)。
- 在onAppear和onDisappear中,根據(jù)起點(diǎn)和終點(diǎn)和終點(diǎn)的TranslationValues,構(gòu)造一個(gè)改變View屬性的Animator,同時(shí)在動(dòng)畫結(jié)束之后,還原它的屬性。
五、總結(jié)
這一篇我們分析了Content Transition的設(shè)計(jì)思想和原理,下一篇文章我們將著重討論如何通過代碼來實(shí)現(xiàn)上面的效果。
六、參考文獻(xiàn)
1.Getting Started with Activity & Fragment Transitions (part 1) 2.Content Transitions In-Depth (part 2) 3.Material-Animations
總結(jié)
以上是生活随笔為你收集整理的动画体系知识梳理(1) 转场动画 ContentTransition 理论篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Dagger2 知识梳理(1) Da
- 下一篇: 建立项目的webpack简单配置