1 View transitions(View轉(zhuǎn)換效果)
1.1 什么是 Transition
溯革?
Transition
的中文翻譯是 “過(guò)渡”灾锯、“轉(zhuǎn)換”蟆淀、“轉(zhuǎn)變”鹦聪,這里對(duì)于 View Transition
個(gè)人粗淺的理解是:View
的場(chǎng)景切換他巨,即 View
的入場(chǎng)和出場(chǎng)充坑。
那么 View transition Aniamtion
就很好理解了,就是當(dāng) view 入場(chǎng)或出場(chǎng)時(shí)觸發(fā)的動(dòng)畫(huà)染突。
1.2 如何實(shí)現(xiàn) View Transition
動(dòng)畫(huà)
不管是自己實(shí)現(xiàn)捻爷,還是 Android 系統(tǒng)已經(jīng)幫我們實(shí)現(xiàn)了的機(jī)制,大概都能總結(jié)為 3 步:
捕獲view在開(kāi)始場(chǎng)景和結(jié)束場(chǎng)景中的狀態(tài)
基于view從一個(gè)場(chǎng)景到另一個(gè)場(chǎng)景的狀態(tài)改變份企,創(chuàng)建動(dòng)畫(huà)
執(zhí)行動(dòng)畫(huà)
1.3 Android Transition 框架是如何實(shí)現(xiàn)動(dòng)畫(huà)的呢也榄?
beginDelayedTransition()
, 傳入view
和Fade
,調(diào)用captureStartValues()
記錄場(chǎng)景中各個(gè) view 的可見(jiàn)性設(shè)置 view 為不可見(jiàn)
在下一幀的時(shí)候司志,F(xiàn)ramework 調(diào)用
captureEndValues()
甜紫,記錄場(chǎng)景中每個(gè) view 的可見(jiàn)性Framework 根據(jù)可見(jiàn)性發(fā)生變化的 view 的前后值,創(chuàng)建并返回
AnimatorSet
Framework 運(yùn)行 Animator骂远,觸發(fā) view 逐漸進(jìn)入或退出場(chǎng)景
其中 Android 已經(jīng)預(yù)定義了
Fade
,Slide
,Explode
如果需要自定義動(dòng)畫(huà)的話(huà)囚霸,需要派生自
Visibility
, 并重載onAppear
和onDisappear
,分別返回View
入場(chǎng)和出場(chǎng)的動(dòng)畫(huà)Animator
-
示例代碼如下:
TransitionManager.beginDelayedTransition(mRootView, new Fade()); toggleVisibility(mRedBox, mGreenBox, mBlueBox, mPurpleBox, mOrangeBox);
private static void toggleVisibility(View... views) { for (View view : views) { boolean isVisible = view.getVisibility() == View.VISIBLE; view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE); } }
說(shuō)明:這里通過(guò)設(shè)置 view 的
visibility
來(lái)觸發(fā) view 的出場(chǎng)和入場(chǎng) 效果如下:
說(shuō)明:紅色點(diǎn)擊對(duì)應(yīng) Fade
, 綠色點(diǎn)擊對(duì)應(yīng) Slide
, 藍(lán)色點(diǎn)擊對(duì)應(yīng) Explode
激才,黃色點(diǎn)擊對(duì)應(yīng)自定義的圓形消失動(dòng)畫(huà)
1.4 基于 Transition Framework 創(chuàng)建動(dòng)畫(huà)的優(yōu)點(diǎn)
簡(jiǎn)單查看如下:
抽象了 Animator 的概念拓型,使用者并不需要知道底層是通過(guò) Animator 實(shí)現(xiàn)的
減少了代碼量,僅需要指定view的前后不同狀態(tài)瘸恼,Transition會(huì)自動(dòng)創(chuàng)建動(dòng)畫(huà)
有利于代碼復(fù)用劣挫,自定義的
View Transition Visibility
可以直接給其他模塊復(fù)用,且能使代碼結(jié)構(gòu)清晰
2 Activity Transitions(Activity 轉(zhuǎn)換效果)
2.1 版本區(qū)別
-
Android 5.0 之前
可以通過(guò)
Activity.overridePendingTransition();
和FragmentTransaction#setCustomAnimation();
來(lái)實(shí)現(xiàn) Activity 之間的切換效果钞脂。示例代碼如下:
activity.finish(); overridePendingTransition(R.anim.anim_fade_in, R.anim.anim_fade_out);
缺點(diǎn):只能對(duì)一個(gè)
Activity
和Fragment
的整個(gè) view 做動(dòng)畫(huà) -
Android 5.0
- 允許對(duì)Activity/Fragment的單個(gè)view做動(dòng)畫(huà)
- 允許定義共享view并創(chuàng)建動(dòng)畫(huà)
2.2 分類(lèi)
-
按觸發(fā)時(shí)間劃分
Exit transition (A啟動(dòng)B, A發(fā)生exit)
Enter transition (A啟動(dòng)B, B發(fā)生enter)
Return transition (B返回A, B發(fā)生return)
Reenter transition (B返回A, A發(fā)生reenter)
-
按內(nèi)容劃分
Content Transition (內(nèi)容切換)
Shared Element Transition (共享元素切換)
2.3 創(chuàng)建 Activity Transition 的步驟
-
在調(diào)用和被調(diào)用的 Activity 中揣云,通過(guò)設(shè)定
Window.FEATURE_ACTIVITY_TRANSITIONS
和Window.FEATURE_CONTENT_TRANSITIONS
來(lái)啟動(dòng)transition api
。具體代碼如下:
java 代碼中設(shè)置
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITION);
style.xml
文件中設(shè)置<item name=“android:windowContentTransitions”>true</item>
注:使用 Material Design 主題的 app 默認(rèn)已啟動(dòng)
-
分別在調(diào)用與被調(diào)用的
Activity
中設(shè)置exit
和enter transition
冰啃。Material
主題默認(rèn)會(huì)將exit
的transition
設(shè)置為null
, 而enter
的transition
設(shè)置成Fade
如果
reenter
或return transition
沒(méi)有明確設(shè)置邓夕,則將用exit
和enter
的transition
替代
-
分別在調(diào)用與被調(diào)用的
Activity
中設(shè)置exit
和enter
共享元素的transition
。Material
主題會(huì)默認(rèn)將exit
的共享元素transition
設(shè)置成null
阎毅,而enter
的共享元素transition
設(shè)置成@android:transition/move
如果
reenter
或return transition
沒(méi)有明確設(shè)置焚刚,則將用exit
和enter
的共享元素transition
替代
-
調(diào)用
startActivity(Context, Bundle)
啟動(dòng)Activity
第二個(gè)參數(shù)
ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();
其中
pairs
參數(shù)是一個(gè)數(shù)組:Pair<View, String>
,該數(shù)組列出了activity
中共享的view
和view
的名稱(chēng)
調(diào)用
finishAfterTransition()
觸發(fā)返回動(dòng)畫(huà)扇调,而不是finish()
-
默認(rèn)情況下矿咕,
Material
主題的 app 中enter
/return
的content transition
會(huì)在exit
/reenter
的content transitions
結(jié)束之前開(kāi)始播放(只是稍微早于)- 可調(diào)用
setWindowAllowEnterTransitionOverlap()
和setWindowAllowReturnTransitionOverlap()
方法屏蔽
- 可調(diào)用
2.4 創(chuàng)建 Fragment Transition 的步驟
頁(yè)面的
exit
,enter
,reenter
, 和return transition
需要調(diào)用 fragment 的相應(yīng)方法來(lái)設(shè)置,或者通過(guò)fragment 的 xml 屬性來(lái)設(shè)置。共享元素的
enter
和return transition
也需要調(diào)用 fragment 的相應(yīng)方法來(lái)設(shè)置碳柱,或者通過(guò) fragment 的 xml 屬性來(lái)設(shè)置捡絮。雖然在 Activity 中 transition 是被
startActivity()
和finishAfterTransition()
觸發(fā)的,但是 fragment 的 transition 卻是在其被FragmentTransaction
執(zhí)行下列動(dòng)作的時(shí)候自動(dòng)發(fā)生的莲镣。added
,removed
,attached
,detached
,shown
,hidden
在 fragment commit 之前福稳,共享元素需要通過(guò)調(diào)用
addSharedElement(View, String)
方法來(lái)成為FragmentTransaction
的一部分
3 Acvitivity - Content Transitions
Content Transition
的概念:Activity 或 Fragment 切換的時(shí)候,定義非共享 view 進(jìn)入或離開(kāi)場(chǎng)景的方式(動(dòng)畫(huà))
相關(guān)的 api 有:setExitTransition()
, setEnterTransition()
, setReturnTransition()
, setReenterTransition()
3.1 Content transition 如何觸發(fā)
假設(shè)從 Activity A 啟動(dòng) B瑞侮,則過(guò)程如下:
-
當(dāng) Activity A 調(diào)用 startActivity
Framework 遍歷 A 的場(chǎng)景樹(shù)的圆,并判斷哪些 view 會(huì)離開(kāi)場(chǎng)景
A 的
exit transition
捕獲transition view
的初始狀態(tài)Framework 設(shè)置全部的
transition view
為INVISIBLE
在下一幀的時(shí)候,A 的
exit transition
捕獲transition view
的終止?fàn)顟B(tài)A 的
exit transition
根據(jù)前后的狀態(tài)改變半火,創(chuàng)建 Animator 后并運(yùn)行
-
當(dāng) Activity B 啟動(dòng)
Framework 遍歷 B 的場(chǎng)景樹(shù)越妈,并判斷哪些 view 會(huì)進(jìn)入,將這些 view 初始化為
INVISIBLE
B 的
enter transition
捕獲transition view
的初始狀態(tài)Framework 設(shè)置全部的
transition view
為VISIBLE
在下一幀的時(shí)候钮糖,B 的
enter transition
捕獲transition view
的終止?fàn)顟B(tài)B 的
enter transition
根據(jù)前后的狀態(tài)改變梅掠,創(chuàng)建Animator
后并運(yùn)行
3.2 演示效果 1
3.3 Content Transition 的對(duì)象
看到這里,相信大家已經(jīng)對(duì) Activity Content Transition 已經(jīng)有了比較清晰的理解了藐鹤。不過(guò)如果仔細(xì)查看演示效果瓤檐,發(fā)現(xiàn)還是有問(wèn)題,那就是頁(yè)面中的 WebView
并沒(méi)有做動(dòng)畫(huà)娱节。那么問(wèn)題來(lái)了:
什么樣的對(duì)象可以當(dāng)做 Transition Objects
能否把 ViewGroup 和它的 children 當(dāng)做一個(gè)整體做動(dòng)畫(huà)
查看源碼:
/** @hide */
@Override
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);
}
}
}
我們會(huì)發(fā)現(xiàn)如果接口 isTransitionGroup()
返回 true
的話(huà)挠蛉,那這個(gè) View
或 ViewGroup
就可以當(dāng)做一個(gè)整體來(lái)對(duì)待。然后再查看下 WebView.isTransitionGroup()
的原始返回值肄满,發(fā)現(xiàn)是 false
谴古。到這里,我們就發(fā)現(xiàn)前面頁(yè)面中的 WebView
并沒(méi)有做動(dòng)畫(huà)的原因了稠歉,由于 Framework 捕獲 WebView
的時(shí)候掰担,isTransitionGroup()
返回 false
, 由此開(kāi)始遍歷 WebView
的子控件,然后 getChildCount()
返回 0
怒炸,那這里可以理解為 WebView
被忽略過(guò)去了带饱,導(dǎo)致我們?cè)O(shè)置的 Content Transition
動(dòng)畫(huà)無(wú)法在 WebView
上生效。我們只需調(diào)用 WebView.setTransitionGroup(true)
就能讓系統(tǒng)捕獲住 WebView
阅羹。
3.4 演示效果 2
說(shuō)明:當(dāng)點(diǎn)擊最下面中間的按鈕之后勺疼,調(diào)用了 setTransitionGroup
之后,WebView
也開(kāi)始跟著做動(dòng)畫(huà)了
4 Acvitivity - Shared Element Transition
4.1 概念及 api
Shared Element Transition
的 概念 :定義的共享 view
捏鱼,在場(chǎng)景切換的時(shí)候执庐,觸發(fā)從場(chǎng)景1運(yùn)動(dòng)到場(chǎng)景2的對(duì)應(yīng)的位置的動(dòng)畫(huà)
相關(guān)的 api 有:
setSharedElementEnterTransition()
setSharedElementReturnTransition()
setSharedElementExitTransition()
setSharedElementReenterTransition()
4.2 Shared Element Transition 觸發(fā)過(guò)程
當(dāng) Activity A 啟動(dòng) B,B 被創(chuàng)建导梆,并且 B 的場(chǎng)景樹(shù)被 measured 和 laid out 在一個(gè)透明的窗口
Framework 將 B 中的
shared element
放置在 A 對(duì)應(yīng)的位置上( resize和reposition )B
enter transition
捕獲shared element
在 B 上的終止?fàn)顟B(tài)B
enter transition
比較shared element
的初始和終止?fàn)顟B(tài)轨淌,創(chuàng)建Animator
Framework 隱藏 A 上的
shared element
迂烁,并運(yùn)行Animator
當(dāng)
Animator
結(jié)束或者將要結(jié)束的時(shí)候,B 的窗口背景色逐漸轉(zhuǎn)為不透明递鹉,并觸發(fā) B 的enter content transition
注:默認(rèn)情況下盟步,在 transition
運(yùn)行過(guò)程中 shared element
在整個(gè)場(chǎng)景樹(shù)的頂層繪制□锝幔可以通過(guò)調(diào)用 Window#setSharedElementsUseOverlay(false)
來(lái)禁用默認(rèn)效果
4.3 Shared Element Transition 對(duì)象
和 Content Transition
不同的是址芯,Shared Element
需要主動(dòng)設(shè)置。
為了將上面的演示結(jié)果中懸浮按鈕指定為一個(gè) Shared Element
窜觉,程序猿需要寫(xiě)如下代碼:
Transition1Activity.java
ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(
Transition1Activity.this,
Pair.create(mFabButton, "fab"));
Transition1Activity.xml
<Button
android:id="@+id/fab_button"
android:layout_width="@dimen/fab_big_size"
android:layout_height="@dimen/fab_big_size"
android:transitionName="fab" />
Transition2Activity.xml
<Button
android:id="@+id/fab_button"
android:layout_width="@dimen/fab_size"
android:layout_height="@dimen/fab_big_size"
android:transitionName="fab" />
說(shuō)明:對(duì)于 mFabButton
來(lái)說(shuō),需要在當(dāng)前 Activity 的 xml 文件中指定 transitionName
北专,和后面跳轉(zhuǎn)的 Activity 的 xml 文件中也指定相同的 transitionName
禀挫。在 java 代碼中,明確指定控件對(duì)象和設(shè)定的 transitionName
拓颓。
4.4 Shared Element Transition 延遲啟動(dòng)
-
為什么需要延遲啟動(dòng)
當(dāng)
shared element
在 Fragment 中语婴,F(xiàn)ragmentTransactions 不會(huì)被立馬執(zhí)行當(dāng)
shared element
是大尺寸高分辨率的圖片時(shí),會(huì)觸發(fā)一次額外的排版當(dāng)
shared element
依賴(lài)于異步加載的數(shù)據(jù)時(shí)驶睦,導(dǎo)致排版不及時(shí)
- 如何延遲啟動(dòng)
Android 給我們提供了 2 個(gè)接口 postponeEnterTransition()
和 startPostponedEnterTransition()
具體使用代碼如下:
postponeEnterTransition();
mSharedElement.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mSharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
mSharedElement.requestLayout();
startPostponedEnterTransition();
return true;
}
});
4.5 演示效果
4.6 注意點(diǎn)
在調(diào)用
postponeEnterTransition
之后砰左,別忘記調(diào)用startPostponeEnterTransition
,忘記調(diào)用將導(dǎo)致 app 進(jìn)入一種死鎖狀態(tài)场航,無(wú)法進(jìn)入下一個(gè) activity/Fragment延遲時(shí)間最好不要超過(guò)1秒鐘