Android?Activity共享元素動(dòng)畫示例解析
正文
所謂Activity
共享元素動(dòng)畫,就是從ActivityA
跳轉(zhuǎn)到ActivityB
通過(guò)控制某些元素(View
)從ActivityA
開(kāi)始幀的位置跳轉(zhuǎn)到ActivityB
結(jié)束幀的位置,應(yīng)用過(guò)度動(dòng)畫
Activity
的共享元素動(dòng)畫,其動(dòng)畫核心是使用的Transition
記錄共享元素的開(kāi)始幀、結(jié)束幀,然后使用TransitionManager
過(guò)度動(dòng)畫管理類調(diào)用beginDelayedTransition
方法 應(yīng)用過(guò)度動(dòng)畫
注意:Android5.0才開(kāi)始支持共享元素動(dòng)畫
所以咱們先介紹一下TransitionManager
的一些基礎(chǔ)知識(shí)
TransitionManager介紹
TransitionManager
是 Android5.0
開(kāi)始提供的一個(gè)過(guò)渡動(dòng)畫管理類,功能非常強(qiáng)大;其可應(yīng)用在兩個(gè)Activity
之間、Fragment
之間、View
之間應(yīng)用過(guò)渡動(dòng)畫
TransitionManager
有兩個(gè)比較重要的類Scene(場(chǎng)景)
和Transition(過(guò)渡)
, 咱們先來(lái)介紹一下這兩個(gè)類
Scene(場(chǎng)景)
顧名思義Scene
就是場(chǎng)景的意思,在執(zhí)行動(dòng)畫之前,我們需要?jiǎng)?chuàng)建兩個(gè)場(chǎng)景(場(chǎng)景A和場(chǎng)景B), 其動(dòng)畫執(zhí)行流程如下:
- 根據(jù)起始布局和結(jié)束布局創(chuàng)建兩個(gè)
Scene
對(duì)象(場(chǎng)景A和場(chǎng)景B); 然而 起始布局的場(chǎng)景通常是根據(jù)當(dāng)前布局自動(dòng)確定的 - 創(chuàng)建一個(gè)
Transition
對(duì)象以定義所需的動(dòng)畫類型 - 調(diào)用
TransitionManager.go(Scene, Transition)
,使用過(guò)渡動(dòng)畫運(yùn)行到指定的場(chǎng)景
生成場(chǎng)景
生成場(chǎng)景有兩種方式; 一種是調(diào)用靜態(tài)方法通過(guò)布局生成 Scene.getSceneForLayout(sceneRoot, R.layout.scene_a, this)
,一種是直接通過(guò)構(gòu)造方法new Scene(sceneRoot, viewHierarchy)
指定view對(duì)象生成
這兩種方式其實(shí)差不多,第一種通過(guò)布局生成的方式在使用的時(shí)候會(huì)自動(dòng)inflate
加載布局生成view
對(duì)象
用法比較簡(jiǎn)單;下面我們來(lái)看一下官方的demo
定義兩個(gè)布局場(chǎng)景A和場(chǎng)景B
<!-- res/layout/scene_a.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(a)" /> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(a)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(a)" /> </LinearLayout>
<!-- res/layout/scene_b.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1(b)" /> <TextView android:id="@+id/text_view2" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2(b)" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3(b)" /> </LinearLayout>
使用場(chǎng)景并執(zhí)行動(dòng)畫
// 創(chuàng)建a場(chǎng)景 val aScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_a, this) // 創(chuàng)建b場(chǎng)景 val bScene: Scene = Scene.getSceneForLayout(binding.sceneRoot, R.layout.scene_b, this) var aSceneFlag = true // 添加點(diǎn)擊事件,切換不同的場(chǎng)景 binding.btClick1.setOnClickListener { if (aSceneFlag) { TransitionManager.go(bScene) aSceneFlag = false } else { TransitionManager.go(aScene) aSceneFlag = true } }
執(zhí)行效果如下:
通過(guò)上面的效果可以看出,切換的一瞬間會(huì)立馬變成指定場(chǎng)景的所有view(文案全都變了),只是應(yīng)用了開(kāi)始幀的位置而已,然后慢慢過(guò)渡到結(jié)束幀的位置;
// Scene的enter()方法源碼 public void enter() { // Apply layout change, if any if (mLayoutId > 0 || mLayout != null) { // remove掉場(chǎng)景根視圖下的所有view(即上一個(gè)場(chǎng)景) getSceneRoot().removeAllViews(); // 添加當(dāng)前場(chǎng)景的所有view if (mLayoutId > 0) { LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); } else { mSceneRoot.addView(mLayout); } } // Notify next scene that it is entering. Subclasses may override to configure scene. if (mEnterAction != null) { mEnterAction.run(); } setCurrentScene(mSceneRoot, this); }
可見(jiàn)切換到指定場(chǎng)景,比如切換到場(chǎng)景b, 會(huì)remove掉場(chǎng)景a的所有view,然后添加場(chǎng)景b的所有view
其次;這種方式的兩種場(chǎng)景之間的切換動(dòng)畫;是通過(guò)id確定兩個(gè)view之間的對(duì)應(yīng)關(guān)系,從而確定view的開(kāi)始幀和結(jié)束幀 來(lái)執(zhí)行過(guò)渡動(dòng)畫;如果沒(méi)有id對(duì)應(yīng)關(guān)系的view(即沒(méi)有開(kāi)始幀或結(jié)束幀), 會(huì)執(zhí)行刪除動(dòng)畫(默認(rèn)是漸隱動(dòng)畫)或添加動(dòng)畫(默認(rèn)是漸顯動(dòng)畫)(看源碼也可以通過(guò)transtionName屬性來(lái)指定對(duì)應(yīng)關(guān)系)
其視圖匹配對(duì)應(yīng)關(guān)系的源碼如下:
// startValues 是開(kāi)始幀所有對(duì)象的屬性 // endValues 是結(jié)束幀所有對(duì)象的屬性 private void matchStartAndEnd(TransitionValuesMaps startValues, TransitionValuesMaps endValues) { ArrayMap<View, TransitionValues> unmatchedStart = new ArrayMap<View, TransitionValues>(startValues.viewValues); ArrayMap<View, TransitionValues> unmatchedEnd = new ArrayMap<View, TransitionValues>(endValues.viewValues); for (int i = 0; i < mMatchOrder.length; i++) { switch (mMatchOrder[i]) { case MATCH_INSTANCE: // 匹配是否相同的對(duì)象(可以通過(guò)改變view的屬性,使用過(guò)渡動(dòng)畫) matchInstances(unmatchedStart, unmatchedEnd); break; case MATCH_NAME: // 匹配transitionName屬性是否相同(activity之間就是通過(guò)transtionName來(lái)匹配的) matchNames(unmatchedStart, unmatchedEnd, startValues.nameValues, endValues.nameValues); break; case MATCH_ID: // 匹配view的id是否相同 matchIds(unmatchedStart, unmatchedEnd, startValues.idValues, endValues.idValues); break; case MATCH_ITEM_ID: // 特殊處理listview的item matchItemIds(unmatchedStart, unmatchedEnd, startValues.itemIdValues, endValues.itemIdValues); break; } } // 添加沒(méi)有匹配到的對(duì)象 addUnmatched(unmatchedStart, unmatchedEnd); }
可見(jiàn)試圖的匹配關(guān)系有很多種;可以根據(jù) 視圖對(duì)象本身、視圖的id、視圖的transitionName屬性等匹配對(duì)應(yīng)關(guān)系
定義場(chǎng)景比較簡(jiǎn)單,其實(shí)相對(duì)比較復(fù)雜的是Transition
過(guò)度動(dòng)畫
缺點(diǎn):個(gè)人覺(jué)得通過(guò)創(chuàng)建不同Scene
對(duì)象實(shí)現(xiàn)動(dòng)畫效果比較麻煩,需要?jiǎng)?chuàng)建多套布局,后期難以維護(hù);所以一般這種使用TransitionManager.go(bScene)
方法指定Scene
對(duì)象的方式基本不常用,一般都是使用TransitionManager.beginDelayedTransition()
方法來(lái)實(shí)現(xiàn)過(guò)渡動(dòng)畫
下面我們來(lái)介紹Transition
,并配合使用TransitionManager.beginDelayedTransition()
方法實(shí)現(xiàn)動(dòng)畫效果
Transition(過(guò)渡)
顧名思義 Transition
是過(guò)渡的意思,里面定義了怎么 記錄開(kāi)始幀的屬性、記錄結(jié)束幀的屬性、創(chuàng)建動(dòng)畫或執(zhí)行動(dòng)畫的邏輯
我們先看看Transition
源碼里比較重要的幾個(gè)方法
// android.transition.Transition的源碼 public abstract class Transition implements Cloneable { ... // 通過(guò)實(shí)現(xiàn)這個(gè)方法記錄view的開(kāi)始幀的屬性 public abstract void captureStartValues(TransitionValues transitionValues); // 通過(guò)實(shí)現(xiàn)這個(gè)方法記錄view的結(jié)束幀的屬性 public abstract void captureEndValues(TransitionValues transitionValues); // 通過(guò)記錄的開(kāi)始幀和結(jié)束幀的屬性,創(chuàng)建動(dòng)畫 // 默認(rèn)返回null,即沒(méi)有動(dòng)畫;需要你自己創(chuàng)建動(dòng)畫對(duì)象 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { return null; } // 執(zhí)行動(dòng)畫 // mAnimators 里包含的就是上面createAnimator()方法創(chuàng)建的動(dòng)畫對(duì)象 protected void runAnimators() { if (DBG) { Log.d(LOG_TAG, "runAnimators() on " + this); } start(); ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); // Now start every Animator that was previously created for this transition for (Animator anim : mAnimators) { if (DBG) { Log.d(LOG_TAG, " anim: " + anim); } if (runningAnimators.containsKey(anim)) { start(); runAnimator(anim, runningAnimators); } } mAnimators.clear(); end(); } ... }
如果我們要自定義Transition
過(guò)渡動(dòng)畫的話,一般只需要重寫前三個(gè)方法即可
當(dāng)前系統(tǒng)也提供了一套完成的Transition
過(guò)渡動(dòng)畫的子類
上面這些都是系統(tǒng)提供的Transition
子類 的 實(shí)現(xiàn)效果 和 其在captureStartValues
、captureEndValues
中記錄的屬性,然后在createAnimator
方法中創(chuàng)建的屬性動(dòng)畫 不斷改變的屬性
當(dāng)然除了上面的一些類以外,系統(tǒng)還提供了TransitionSet
類,可以指定一組動(dòng)畫;它也是的Transition
子類
其TransitionManager
中的默認(rèn)動(dòng)畫就是 AutoTransition
, 它是TransitionSet
的子類
public class AutoTransition extends TransitionSet { public AutoTransition() { init(); } public AutoTransition(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setOrdering(ORDERING_SEQUENTIAL); addTransition(new Fade(Fade.OUT)). addTransition(new ChangeBounds()). addTransition(new Fade(Fade.IN)); } }
可見(jiàn)AutoTransition
包含了 淡出、位移、改變大小、淡入 等一組效果
下面我們來(lái)自定義一些Transition
,達(dá)到一些效果
// 記錄和改變translationX、translationY屬性 class XYTranslation : Transition() { override fun captureStartValues(transitionValues: TransitionValues?) { transitionValues ?: return transitionValues.values["translationX"] = transitionValues.view.translationX transitionValues.values["translationY"] = transitionValues.view.translationY } override fun captureEndValues(transitionValues: TransitionValues?) { transitionValues ?: return transitionValues.values["translationX"] = transitionValues.view.translationX transitionValues.values["translationY"] = transitionValues.view.translationY } override fun createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { if (startValues == null || endValues == null) return null val startX = startValues.values["translationX"] as Float val startY = startValues.values["translationY"] as Float val endX = endValues.values["translationX"] as Float val endY = endValues.values["translationY"] as Float var translationXAnim: Animator? = null if (startX != endX) { translationXAnim = ObjectAnimator.ofFloat(endValues.view, "translationX", startX, endX) } var translationYAnim: Animator? = null if (startY != endY) { translationYAnim = ObjectAnimator.ofFloat(endValues.view, "translationY", startY, endY) } return mergeAnimators(translationXAnim, translationYAnim) } fun mergeAnimators(animator1: Animator?, animator2: Animator?): Animator? { return if (animator1 == null) { animator2 } else if (animator2 == null) { animator1 } else { val animatorSet = AnimatorSet() animatorSet.playTogether(animator1, animator2) animatorSet } } }
// 記錄和改變backgroundColor屬性 class BackgroundColorTransition : Transition() { override fun captureStartValues(transitionValues: TransitionValues?) { transitionValues ?: return val drawable = transitionValues.view.background as? ColorDrawable ?: return transitionValues.values["backgroundColor"] = drawable.color } override fun captureEndValues(transitionValues: TransitionValues?) { transitionValues ?: return val drawable = transitionValues.view.background as? ColorDrawable ?: return transitionValues.values["backgroundColor"] = drawable.color } override fun createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { if (startValues == null || endValues == null) return null val startColor = (startValues.values["backgroundColor"] as? Int) ?: return null val endColor = (endValues.values["backgroundColor"] as? Int) ?: return null if (startColor != endColor) { return ObjectAnimator.ofArgb(endValues.view, "backgroundColor", startColor, endColor) } return super.createAnimator(sceneRoot, startValues, endValues) } }
非常簡(jiǎn)單,上面就自定義了兩個(gè)XYTranslation
和BackgroundColorTransition
類,實(shí)現(xiàn)位移和改變背景顏色的效果
下面我們配合使用TransitionManager.beginDelayedTransition()
方法,應(yīng)用XYTranslation
和BackgroundColorTransition
兩個(gè)動(dòng)畫過(guò)渡類,實(shí)現(xiàn)效果
<!-- res/layout/activity_main.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btClick" android:layout_width="100dp" android:layout_height="wrap_content" android:text="執(zhí)行動(dòng)畫"/> <LinearLayout android:id="@+id/beginDelayRoot" android:layout_width="match_parent" android:layout_height="180dp" android:background="#ffff00" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 1" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 2" /> <TextView android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:text="Text Line 3" /> </LinearLayout> </LinearLayout>
class TransitionDemoActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) val backgroundColor1 = Color.parseColor("#ffff00") val backgroundColor2 = Color.parseColor("#00ff00") var index = 0 binding.btClick.setOnClickListener { val view1 = binding.beginDelayRoot.getChildAt(0) val view2 = binding.beginDelayRoot.getChildAt(1) // 設(shè)置開(kāi)始位置的x偏移量為100px(定義開(kāi)始幀的屬性) view1.translationX = 100f view2.translationX = 100f // 調(diào)用beginDelayedTransition 會(huì)立馬調(diào)用 Transition的captureStartValues方法記錄開(kāi)始幀 // 同時(shí)會(huì)添加一個(gè)OnPreDrawListener, 在屏幕刷新的下一幀觸發(fā)onPreDraw() 方法,然后調(diào)用captureEndValues方法記錄結(jié)束幀,然后開(kāi)始執(zhí)行動(dòng)畫 TransitionManager.beginDelayedTransition(binding.beginDelayRoot, TransitionSet().apply { // 實(shí)現(xiàn)上下移動(dòng)(因?yàn)闆](méi)有改變view的left屬性所以, 所以它沒(méi)有左右移動(dòng)效果) addTransition(ChangeBounds()) // 通過(guò)translationX屬性實(shí)現(xiàn)左右移動(dòng) addTransition(XYTranslation()) // 通過(guò)backgroundColor屬性改變背景顏色 addTransition(BackgroundColorTransition()) }) // 下面開(kāi)始改變視圖的屬性(定義結(jié)束幀的屬性) // 將結(jié)束位置x偏移量為0px view1.translationX = 0f view2.translationX = 0f binding.beginDelayRoot.removeView(view1) binding.beginDelayRoot.removeView(view2) binding.beginDelayRoot.addView(view1) binding.beginDelayRoot.addView(view2) binding.beginDelayRoot.setBackgroundColor(if (index % 2 == 0) backgroundColor2 else backgroundColor1) index++ } } }
其效果圖如下:
你可能會(huì)有些疑問(wèn),為什么上面將translationX設(shè)置成100之后,立馬又改成了0;這樣有什么意義嗎??
可見(jiàn)Transition
的使用和自定義也比較簡(jiǎn)單,同時(shí)也能達(dá)到一些比較炫酷的效果
請(qǐng)注意,改變view的屬性并不會(huì)立馬重新繪制視圖,而是在屏幕的下一幀(60fps的話,就是16毫秒一幀)去繪制;而在繪制下一幀之前調(diào)用了TransitionManager.beginDelayedTransition()
方法,里面會(huì)觸發(fā)XYTransition
的captureStartValues
方法記錄開(kāi)始幀(記錄的translationX
為100),同時(shí)TransitionManager
會(huì)添加OnPreDrawListener
, 在屏幕下一幀到來(lái)觸發(fā)view去繪制的時(shí)候,會(huì)先調(diào)用OnPreDrawListener
的onPreDraw()
方法,里面又會(huì)觸發(fā)XYTransition
的captureEndValues
方法記錄結(jié)束幀的屬性(記錄的translationX
為0), 然后應(yīng)用動(dòng)畫 改變view的屬性,最后交給view去繪制
上面講了這么多,下面我們簡(jiǎn)單分析一下TransitionManager.beginDelayedTransition
方法的源碼
首先是TransitionManager
的beginDelayedTransition
方法
// android.transition.TransitionManager源碼 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) { if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) { if (Transition.DBG) { Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + sceneRoot + ", " + transition); } sPendingTransitions.add(sceneRoot); if (transition == null) { transition = sDefaultTransition; } final Transition transitionClone = transition.clone(); sceneChangeSetup(sceneRoot, transitionClone); Scene.setCurrentScene(sceneRoot, null); sceneChangeRunTransition(sceneRoot, transitionClone); } }
里面代碼比較少;我們主要看sceneChangeSetup
和sceneChangeRunTransition
方法的實(shí)現(xiàn)
// android.transition.TransitionManager源碼 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { // Capture current values ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); if (runningTransitions != null && runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { // 暫停正在運(yùn)行的動(dòng)畫 runningTransition.pause(sceneRoot); } } if (transition != null) { // 調(diào)用transition.captureValues; // 其實(shí)第二個(gè)參數(shù) true或false,表示是開(kāi)始還是結(jié)束,對(duì)應(yīng)會(huì)調(diào)用captureStartValues和captureEndValues 方法 transition.captureValues(sceneRoot, true); } ... }
我們來(lái)簡(jiǎn)單看看transition.captureValues
的源碼
// android.transition.Transition源碼 void captureValues(ViewGroup sceneRoot, boolean start) { clearValues(start); // 如果你的 Transition 指定了目標(biāo)view,就會(huì)執(zhí)行這個(gè)if if ((mTargetIds.size() > 0 || mTargets.size() > 0) && (mTargetNames == null || mTargetNames.isEmpty()) && (mTargetTypes == null || mTargetTypes.isEmpty())) { for (int i = 0; i < mTargetIds.size(); ++i) { int id = mTargetIds.get(i); View view = sceneRoot.findViewById(id); if (view != null) { TransitionValues values = new TransitionValues(view); if (start) { // 記錄開(kāi)始幀的屬性 captureStartValues(values); } else { // 記錄結(jié)束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開(kāi)始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結(jié)束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } } for (int i = 0; i < mTargets.size(); ++i) { View view = mTargets.get(i); TransitionValues values = new TransitionValues(view); if (start) { // 記錄開(kāi)始幀的屬性 captureStartValues(values); } else { // 記錄結(jié)束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開(kāi)始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結(jié)束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } } else { // 沒(méi)有指定目標(biāo)view的情況 captureHierarchy(sceneRoot, start); } ... } private void captureHierarchy(View view, boolean start) { ... if (view.getParent() instanceof ViewGroup) { TransitionValues values = new TransitionValues(view); if (start) { // 記錄開(kāi)始幀的屬性 captureStartValues(values); } else { // 記錄結(jié)束幀的屬性 captureEndValues(values); } values.targetedTransitions.add(this); capturePropagationValues(values); if (start) { // 緩存開(kāi)始幀的屬性到mStartValues中 addViewValues(mStartValues, view, values); } else { // 緩存結(jié)束幀的屬性到mEndValues中 addViewValues(mEndValues, view, values); } } if (view instanceof ViewGroup) { // 遞歸遍歷所有的children ViewGroup parent = (ViewGroup) view; for (int i = 0; i < parent.getChildCount(); ++i) { captureHierarchy(parent.getChildAt(i), start); } } }
可見(jiàn)sceneChangeSetup
方法就會(huì)觸發(fā)Transition
的captureStartValues
方法
接下來(lái)我們來(lái)看看sceneChangeRunTransition
方法
// android.transition.TransitionManager源碼 private static void sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition) { if (transition != null && sceneRoot != null) { MultiListener listener = new MultiListener(transition, sceneRoot); sceneRoot.addOnAttachStateChangeListener(listener); // 添加OnPreDrawListener sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); } } private static class MultiListener implements ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener { ... @Override public boolean onPreDraw() { removeListeners(); ... // 這里就會(huì)觸發(fā)captureEndValues方法,記錄結(jié)束幀的屬性 mTransition.captureValues(mSceneRoot, false); if (previousRunningTransitions != null) { for (Transition runningTransition : previousRunningTransitions) { runningTransition.resume(mSceneRoot); } } // 開(kāi)始執(zhí)行動(dòng)畫 // 這里就會(huì)調(diào)用 Transition的createAnimator方法 和 runAnimators方法 mTransition.playTransition(mSceneRoot); return true; } };
Transition
的playTransition
沒(méi)啥好看的,至此TransitionManager
的beginDelayedTransition
源碼分析到這里
上面源碼里你可能也看到了Transition
可以設(shè)置目標(biāo)視圖,應(yīng)用過(guò)渡動(dòng)畫, 主要是通過(guò)addTarget
方法實(shí)現(xiàn)的,如果沒(méi)有設(shè)置目標(biāo)視圖,默認(rèn)就會(huì)遍歷所有的children應(yīng)用在所有的視圖上
OverlayView和ViewGroupOverlay
OverlayView
和ViewGroupOverlay
是Activity
共享元素動(dòng)畫實(shí)現(xiàn)里比較重要的一個(gè)類,所以就單獨(dú)的介紹一下
OverlayView
是針對(duì)View
的一個(gè)頂層附加層(即遮罩層),它在View的所有內(nèi)容繪制完成之后 再繪制
ViewGroupOverlay
是針對(duì)ViewGroup
的,是OverlayView
的子類,它在ViewGroup
的所有內(nèi)容(包括所有的children)繪制完成之后 再繪制
// android.view.View源碼 public ViewOverlay getOverlay() { if (mOverlay == null) { mOverlay = new ViewOverlay(mContext, this); } return mOverlay; }
// android.view.ViewGroup源碼 @Override public ViewGroupOverlay getOverlay() { if (mOverlay == null) { mOverlay = new ViewGroupOverlay(mContext, this); } return (ViewGroupOverlay) mOverlay; }
看上面的源碼我們知道,可以直接調(diào)用getOverlay
方法直接獲取OverlayView
或ViewGroupOverlay
對(duì)象, 然后我們就可以在上面添加一些裝飾等效果
OverlayView
只支持添加drawable
ViewGroupOverlay
支持添加View
和 drawable
注意:如果View
的parent不為null, 則會(huì)自動(dòng)先把它從parent中remove掉,然后添加到ViewGroupOverlay
中
核心源碼如下:
// OverlayViewGroup的add方法源碼 public void add(@NonNull View child) { if (child == null) { throw new IllegalArgumentException("view must be non-null"); } if (child.getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) child.getParent(); ... // 將child從原來(lái)的parent中remove掉 parent.removeView(child); if (parent.getLayoutTransition() != null) { // LayoutTransition will cause the child to delay removal - cancel it parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING); } // fail-safe if view is still attached for any reason if (child.getParent() != null) { child.mParent = null; } } super.addView(child); }
用法也非常簡(jiǎn)單,我們來(lái)看看一個(gè)簡(jiǎn)單的demo
class OverlayActivity : AppCompatActivity() { private lateinit var binding: ActivityOverlayBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityOverlayBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) addViewToOverlayView() addDrawableToOverlayView() binding.btClick.setOnClickListener { // 測(cè)試一下OverlayView的動(dòng)畫 TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation()) binding.llOverlayContainer.translationX += 100 } } private fun addViewToOverlayView() { val view = View(this) view.layoutParams = LinearLayout.LayoutParams(100, 100) view.setBackgroundColor(Color.parseColor("#ff00ff")) // 需要手動(dòng)調(diào)用layout,不然view顯示不出來(lái) view.layout(0, 0, 100, 100) binding.llOverlayContainer.overlay.add(view) } private fun addDrawableToOverlayView() { binding.view2.post { val drawable = ContextCompat.getDrawable(this, R.mipmap.ic_temp) // 需要手動(dòng)調(diào)用setBounds,不然drawable顯示不出來(lái) drawable!!.setBounds(binding.view2.width / 2, 0, binding.view2.width, binding.view2.height / 2) binding.view2.overlay.add(drawable) } } }
效果圖如下:
這里唯一需要注意的是,如果是添加view
,需要手動(dòng)調(diào)用layout
布局,不然view
顯示不出來(lái);如果添加的是drawable
需要手動(dòng)調(diào)用setBounds
,不然drawable
也顯示不出來(lái)
GhostView
GhostView
是Activity
共享元素動(dòng)畫實(shí)現(xiàn)里比較重要的一個(gè)類,所以就單獨(dú)的介紹一下
它的作用是在不改變view
的parent
的情況下,將view
繪制在另一個(gè)parent
下
我們先看看GhostView
的部分源碼
public class GhostView extends View { private final View mView; private int mReferences; private boolean mBeingMoved; private GhostView(View view) { super(view.getContext()); mView = view; mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); // 這句代碼 讓mView在原來(lái)的parent中隱藏(即不繪制視圖) mView.setTransitionVisibility(View.INVISIBLE); parent.invalidate(); } @Override public void setVisibility(@Visibility int visibility) { super.setVisibility(visibility); if (mView.mGhostView == this) { // 如果view在ghostview中繪制(可見(jiàn)),則設(shè)置在原來(lái)的parent不繪制(不可見(jiàn)) // 如果view在ghostview中不繪制(不可見(jiàn)),則設(shè)置在原來(lái)的parent繪制(可見(jiàn)) int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE; mView.setTransitionVisibility(inverseVisibility); } } }
看源碼得知 如果把View
添加到GhostView
里,則默認(rèn)會(huì)調(diào)用view
的setTransitionVisibility
方法 將view
設(shè)置成在parent
中不可見(jiàn), 在GhostView
里可見(jiàn);調(diào)用GhostView
的setVisibility
方法設(shè)置 要么在GhostView
中可見(jiàn),要么在parent
中可見(jiàn)
系統(tǒng)內(nèi)部是使用GhostView.addGhost
靜態(tài)方法添加GhostView
的
我們來(lái)看看添加GhostView
的addGhost
靜態(tài)方法源碼
public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) { if (!(view.getParent() instanceof ViewGroup)) { throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup"); } // 獲取 ViewGroupOverlay ViewGroupOverlay overlay = viewGroup.getOverlay(); ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup; GhostView ghostView = view.mGhostView; int previousRefCount = 0; if (ghostView != null) { View oldParent = (View) ghostView.getParent(); ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent(); if (oldGrandParent != overlayViewGroup) { previousRefCount = ghostView.mReferences; oldGrandParent.removeView(oldParent); ghostView = null; } } if (ghostView == null) { if (matrix == null) { matrix = new Matrix(); calculateMatrix(view, viewGroup, matrix); } // 創(chuàng)建GhostView ghostView = new GhostView(view); ghostView.setMatrix(matrix); FrameLayout parent = new FrameLayout(view.getContext()); parent.setClipChildren(false); copySize(viewGroup, parent); // 設(shè)置GhostView的大小 copySize(viewGroup, ghostView); // 將ghostView添加到了parent中 parent.addView(ghostView); ArrayList<View> tempViews = new ArrayList<View>(); int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews); // 將parent添加到了ViewGroupOverlay中 insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost); ghostView.mReferences = previousRefCount; } else if (matrix != null) { ghostView.setMatrix(matrix); } ghostView.mReferences++; return ghostView; }
可見(jiàn)內(nèi)部的實(shí)現(xiàn)最終將GhostView
添加到了ViewGroupOverlay
(遮罩層)里
配合GhostView
,同時(shí)也解決了ViewGroupOverlay
會(huì)將view
從parent
中remove
的問(wèn)題(即可同時(shí)在ViewGroupOverlay
和原來(lái)的parent
中繪制)
我們來(lái)看看一個(gè)簡(jiǎn)單的demo
class GhostViewActivity : AppCompatActivity() { private lateinit var binding: ActivityGhostViewBinding private var ghostView: View? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityGhostViewBinding.inflate(LayoutInflater.from(this)) setContentView(binding.root) binding.btClick.setOnClickListener { // 配合動(dòng)畫看看效果 TransitionManager.beginDelayedTransition(binding.llOverlayContainer, XYTranslation()) binding.llOverlayContainer.translationX += 100 } binding.btClick2.setOnClickListener { val ghostView = ghostView ?: return@setOnClickListener // 測(cè)試一下ghostView的setVisibility方法效果 if (ghostView.isVisible) { ghostView.visibility = View.INVISIBLE } else { ghostView.visibility = View.VISIBLE } (binding.view1.parent as ViewGroup).invalidate() } binding.view1.post { // 創(chuàng)建一個(gè)GhostView添加到window.decorView的ViewGroupOverlay中 ghostView = addGhost(binding.view1, window.decorView as ViewGroup) } } // 我們無(wú)法直接使用GhostView,只能臨時(shí)使用反射看看效果 private fun addGhost(view: View, viewGroup: ViewGroup): View { val ghostViewClass = Class.forName("android.view.GhostView") val addGhostMethod: Method = ghostViewClass.getMethod( "addGhost", View::class.java, ViewGroup::class.java, Matrix::class.java ) return addGhostMethod.invoke(null, view, viewGroup, null) as View } }
效果圖如下:
可見(jiàn)使用GhostView
并通過(guò)setVisibility
方法,實(shí)現(xiàn)的效果是 既可以在window.decorView
的ViewGroupOverlay
中繪制,也可以在原來(lái)的parent
中繪制
那怎么同時(shí)繪制呢?
只需要在addGhost
之后強(qiáng)制設(shè)置view
的setTransitionVisibility
為View.VISIBLE
即可
binding.view1.post { ghostView = addGhost(binding.view1, window.decorView as ViewGroup) // android 10 之前setTransitionVisibility是hide方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { binding.view1.setTransitionVisibility(View.VISIBLE) (binding.view1.parent as ViewGroup).invalidate() } }
效果圖如下:
Activity的共享元素源碼分析
好的,上面的準(zhǔn)備工作做完了之后,下面我們來(lái)真正的分析Activity
的共享元素源碼
我們先以ActivityA
打開(kāi)ActivityB
為例
先是調(diào)用ActivityOptions.makeSceneTransitionAnimation
創(chuàng)建包含共享元素的ActivityOptions
對(duì)象
//android.app.ActivityOptions類的源碼 public class ActivityOptions { ... public static ActivityOptions makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements) { ActivityOptions opts = new ActivityOptions(); // activity.mExitTransitionListener是SharedElementCallback對(duì)象 makeSceneTransitionAnimation(activity, activity.getWindow(), opts, activity.mExitTransitionListener, sharedElements); return opts; } }
其中activity
的mExitTransitionListener
是SharedElementCallback
對(duì)象,默認(rèn)值是SharedElementCallback.NULL_CALLBACK
,使用的是默認(rèn)實(shí)現(xiàn);可以調(diào)用Activity
的setExitSharedElementCallback
方法設(shè)置這個(gè)對(duì)象, 但是大多數(shù)情況下用默認(rèn)的即可
下面我們來(lái)簡(jiǎn)單介紹下SharedElementCallback
的一些回調(diào)在什么情況下觸發(fā)
public abstract class SharedElementCallback { ... static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() { }; /** * 共享元素 開(kāi)始幀準(zhǔn)備好了 觸發(fā) * @param sharedElementNames 共享元素名稱 * @param sharedElements 共享元素view,并且已經(jīng)將開(kāi)始幀的屬性應(yīng)用到view里了 * @param sharedElementSnapshots 調(diào)用SharedElementCallback.onCreateSnapshotView方法創(chuàng)建的快照 **/ public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * 共享元素 結(jié)束幀準(zhǔn)備好了 觸發(fā) * @param sharedElementNames 共享元素名稱 * @param sharedElements 共享元素view,并且已經(jīng)將結(jié)束幀的屬性應(yīng)用到view里了 * @param sharedElementSnapshots 調(diào)用SharedElementCallback.onCreateSnapshotView方法創(chuàng)建的快照 * 注意:跟onSharedElementStart方法的sharedElementSnapshots參數(shù)是同一個(gè)對(duì)象 */ public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {} /** * 比如在ActivityA存在,而在ActivityB不存在的共享元素 回調(diào) * @param rejectedSharedElements 在ActivityB中不存在的共享元素 **/ public void onRejectSharedElements(List<View> rejectedSharedElements) {} /** * 需要做動(dòng)畫的共享元素映射關(guān)系準(zhǔn)備好之后 回調(diào) * @param names 支持的所有共享元素名稱(是ActivityA打開(kāi)ActivityB時(shí)傳過(guò)來(lái)的所有共享元素名稱) * @param sharedElements 需要做動(dòng)畫的共享元素名稱及view的對(duì)應(yīng)關(guān)系 * 注意:比如ActivityA打開(kāi)ActivityB,對(duì)于ActivityA中的回調(diào) names和sharedElements的大小基本上是一樣的,ActivityB中的回調(diào)就可能會(huì)不一樣 **/ public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {} /** * 將共享元素 view 生成 bitmap 保存在Parcelable中,最終這個(gè)Parcelable會(huì)保存在sharedElementBundle中 * 如果是ActivityA打開(kāi)ActivityB, 則會(huì)把sharedElementBundle傳給ActivityB **/ public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) { ... } /** * 根據(jù)snapshot反過(guò)來(lái)創(chuàng)建view * 如果是ActivityA打開(kāi)ActivityB, ActivityB接收到Parcelable對(duì)象后,在適當(dāng)?shù)臅r(shí)候會(huì)調(diào)用這個(gè)方法創(chuàng)建出view對(duì)象 **/ public View onCreateSnapshotView(Context context, Parcelable snapshot) { ... } /** * 當(dāng)共享元素和sharedElementBundle對(duì)象都已經(jīng)傳第給對(duì)方的時(shí)候觸發(fā)(表明接下來(lái)可以準(zhǔn)備執(zhí)行過(guò)場(chǎng)動(dòng)畫了) * 比如: ActivityA 打開(kāi) ActivityB, ActivityA調(diào)用完onCaptureSharedElementSnapshot將信息保存在sharedElementBundle中,然后傳給ActivityB,這個(gè)時(shí)候ActivityA 和 ActivityB的SharedElementCallback都會(huì)觸發(fā)onSharedElementsArrived方法 **/ public void onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, OnSharedElementsReadyListener listener) { listener.onSharedElementsReady(); } }
SharedElementCallback
的每個(gè)回調(diào)方法的大致意思是這樣的
接下來(lái)我門繼續(xù)往下看源碼 makeSceneTransitionAnimation
//android.app.ActivityOptions類的源碼 public class ActivityOptions { ... static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window, ActivityOptions opts, SharedElementCallback callback, Pair<View, String>[] sharedElements) { // activity的window一定要添加Window.FEATURE_ACTIVITY_TRANSITIONS特征 if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { opts.mAnimationType = ANIM_DEFAULT; return null; } opts.mAnimationType = ANIM_SCENE_TRANSITION; ArrayList<String> names = new ArrayList<String>(); ArrayList<View> views = new ArrayList<View>(); if (sharedElements != null) { for (int i = 0; i < sharedElements.length; i++) { Pair<View, String> sharedElement = sharedElements[i]; String sharedElementName = sharedElement.second; if (sharedElementName == null) { throw new IllegalArgumentException("Shared element name must not be null"); } names.add(sharedElementName); View view = sharedElement.first; if (view == null) { throw new IllegalArgumentException("Shared element must not be null"); } views.add(sharedElement.first); } } //創(chuàng)建ActivityA退出時(shí)的過(guò)場(chǎng)動(dòng)畫核心類 ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window, callback, names, names, views, false); //注意 這個(gè)opts保存了ActivityA的exit對(duì)象,到時(shí)候會(huì)傳給ActivityB的EnterTransitionCoordinator對(duì)象 opts.mTransitionReceiver = exit; // 支持的共享元素名稱 opts.mSharedElementNames = names; // 是否是返回 opts.mIsReturning = (activity == null); if (activity == null) { opts.mExitCoordinatorIndex = -1; } else { // 將exit添加到mActivityTransitionState對(duì)象中,然后由ActivityTransitionState對(duì)象管理和調(diào)用exit對(duì)象里的方法 opts.mExitCoordinatorIndex = activity.mActivityTransitionState.addExitTransitionCoordinator(exit); } return exit; } }
接下來(lái)我們來(lái)看看ExitTransitionCoordinator
這個(gè)類的構(gòu)造函數(shù)干了啥
// android.app.ActivityTransitionCoordinator源碼 abstract class ActivityTransitionCoordinator extends ResultReceiver { ... public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, SharedElementCallback listener, boolean isReturning) { super(new Handler()); mWindow = window; // activity里的SharedElementCallback對(duì)象 mListener = listener; // 支持的所有共享元素名稱 // 比如ActivityA打開(kāi)ActivityB,則是makeSceneTransitionAnimation方法傳過(guò)來(lái)的共享元素名稱 mAllSharedElementNames = allSharedElementNames; // 是否是返回 mIsReturning = isReturning; } } // android.app.ExitTransitionCoordinator源碼 public class ExitTransitionCoordinator extends ActivityTransitionCoordinator { public ExitTransitionCoordinator(ExitTransitionCallbacks exitCallbacks, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { super(window, names, listener, isReturning); // viewsReady主要有以下作用 // 1. 準(zhǔn)備好需要執(zhí)行動(dòng)畫的共享元素,并排序 保存在mSharedElementNames和mSharedElements中 // 2. 準(zhǔn)備好需要做退出動(dòng)畫的非共享元素,保存在mTransitioningViews中 // 3. 這里會(huì)觸發(fā) SharedElementCallback的onMapSharedElements回調(diào) viewsReady(mapSharedElements(accepted, mapped)); // 將mTransitioningViews中的不在屏幕內(nèi)的非共享元素剔除掉 stripOffscreenViews(); mIsBackgroundReady = !isReturning; mExitCallbacks = exitCallbacks; } }
這里比較重要的方法就是viewsReady
方法,核心作用就是我上面說(shuō)的
// android.app.ActivityTransitionCoordinator源碼 protected void viewsReady(ArrayMap<String, View> sharedElements) { // 剔除掉不在mAllSharedElementNames中共享元素 sharedElements.retainAll(mAllSharedElementNames); if (mListener != null) { // 執(zhí)行SharedElementCallback的onMapSharedElements回調(diào) mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); } // 共享元素排序 setSharedElements(sharedElements); if (getViewsTransition() != null && mTransitioningViews != null) { ViewGroup decorView = getDecor(); if (decorView != null) { // 遍歷decorView收集非共享元素 decorView.captureTransitioningViews(mTransitioningViews); } // 移除掉其中的共享元素 mTransitioningViews.removeAll(mSharedElements); } setEpicenter(); }
準(zhǔn)備好ActivityOptions
參數(shù)后,就可以調(diào)用startActivity(Intent intent, @Nullable Bundle options)
方法了,然后就會(huì)調(diào)用到activity
的cancelInputsAndStartExitTransition
方法
// android.app.Activity源碼 private void cancelInputsAndStartExitTransition(Bundle options) { final View decor = mWindow != null ? mWindow.peekDecorView() : null; if (decor != null) { decor.cancelPendingInputEvents(); } if (options != null) { // 開(kāi)始處理ActivityA的退場(chǎng)動(dòng)畫 mActivityTransitionState.startExitOutTransition(this, options); } }
// android.app.ActivityTransitionState源碼 public void startExitOutTransition(Activity activity, Bundle options) { mEnterTransitionCoordinator = null; if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) || mExitTransitionCoordinators == null) { return; } ActivityOptions activityOptions = new ActivityOptions(options); if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { int key = activityOptions.getExitCoordinatorKey(); int index = mExitTransitionCoordinators.indexOfKey(key); if (index >= 0) { mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get(); mExitTransitionCoordinators.removeAt(index); if (mCalledExitCoordinator != null) { mExitingFrom = mCalledExitCoordinator.getAcceptedNames(); mExitingTo = mCalledExitCoordinator.getMappedNames(); mExitingToView = mCalledExitCoordinator.copyMappedViews(); // 調(diào)用ExitTransitionCoordinator的startExit mCalledExitCoordinator.startExit(); } } } }
這里startExitOutTransition
里面就會(huì)調(diào)用ExitTransitionCoordinator
的startExit
方法
// android.app.ExitTransitionCoordinator源碼 public void startExit() { if (!mIsExitStarted) { backgroundAnimatorComplete(); mIsExitStarted = true; pauseInput(); ViewGroup decorView = getDecor(); if (decorView != null) { decorView.suppressLayout(true); } // 將共享元素用GhostView包裹,然后添加的Activity的decorView的OverlayView中 moveSharedElementsToOverlay(); startTransition(this::beginTransitions); } }
這里的moveSharedElementsToOverlay
方法比較重要,會(huì)使用到最開(kāi)始介紹的GhostView
和OverlayView
,目的是將共享元素繪制到最頂層
然后開(kāi)始執(zhí)行beginTransitions
方法
// android.app.ExitTransitionCoordinator源碼 private void beginTransitions() { // 獲取共享元素的過(guò)渡動(dòng)畫類Transition,可以通過(guò)window.setSharedElementExitTransition方法設(shè)置 // 一般不需要設(shè)置 有默認(rèn)值 Transition sharedElementTransition = getSharedElementExitTransition(); // 獲取非共享元素的過(guò)渡動(dòng)畫類Transition,也可以通過(guò)window.setExitTransition方法設(shè)置 Transition viewsTransition = getExitTransition(); // 將sharedElementTransition和viewsTransition合并成一個(gè) TransitionSet Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); ViewGroup decorView = getDecor(); if (transition != null && decorView != null) { setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); if (viewsTransition != null) { setTransitioningViewsVisiblity(View.VISIBLE, false); } // 開(kāi)始采集開(kāi)始幀和結(jié)束幀,執(zhí)行過(guò)度動(dòng)畫 TransitionManager.beginDelayedTransition(decorView, transition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (viewsTransition != null) { setTransitioningViewsVisiblity(View.INVISIBLE, false); } decorView.invalidate(); } else { transitionStarted(); } }
這里在TransitionManager.beginDelayedTransition
的前后都有屌用setGhostVisibility
和scheduleGhostVisibilityChange
方法,是為了采集前后幀的屬性,執(zhí)行過(guò)度動(dòng)畫,采集完成之后,會(huì)顯示GhostView
,而隱藏原來(lái)parent里的共享元素view
上面的sharedElementTransition
和viewsTransition
都添加了監(jiān)聽(tīng)器,在動(dòng)畫結(jié)束之后分別調(diào)用sharedElementTransitionComplete
和viewsTransitionComplete
方法
// android.app.ExitTransitionCoordinator源碼 @Override protected void sharedElementTransitionComplete() { // 這里就會(huì)采集共享元素當(dāng)前的屬性(大小、位置等),會(huì)觸發(fā)SharedElementCallback.onCaptureSharedElementSnapshot方法 mSharedElementBundle = mExitSharedElementBundle == null ? captureSharedElementState() : captureExitSharedElementsState(); super.sharedElementTransitionComplete(); } // android.app.ActivityTransitionCoordinator源碼 protected void viewsTransitionComplete() { mViewsTransitionComplete = true; startInputWhenTransitionsComplete(); }
然后在startInputWhenTransitionsComplete
方法里會(huì)調(diào)用onTransitionsComplete
方法,最終會(huì)調(diào)用notifyComplete
方法
// android.app.ExitTransitionCoordinator源碼 protected boolean isReadyToNotify() { // 1. 調(diào)用完sharedElementTransitionComplete后,mSharedElementBundle不為null // 2. mResultReceiver是在ActivityB創(chuàng)建完EnterTransitionCoordinator之后,發(fā)送MSG_SET_REMOTE_RECEIVER消息 將EnterTransitionCoordinator傳給ActivityA之后不為null // 3. 看構(gòu)造函數(shù),一開(kāi)始就為true return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; } protected void notifyComplete() { if (isReadyToNotify()) { if (!mSharedElementNotified) { mSharedElementNotified = true; // 延遲發(fā)送一個(gè)MSG_CANCEL消息,清空各種狀態(tài)等 delayCancel(); if (!mActivity.isTopOfTask()) { // mResultReceiver是ActivityB的EnterTransitionCoordinator對(duì)象 mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null); } if (mListener == null) { mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); notifyExitComplete(); } else { final ResultReceiver resultReceiver = mResultReceiver; final Bundle sharedElementBundle = mSharedElementBundle; // 觸發(fā)SharedElementCallback.onSharedElementsArrived mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, new OnSharedElementsReadyListener() { @Override public void onSharedElementsReady() { // 發(fā)送MSG_TAKE_SHARED_ELEMENTS,將共享元素的sharedElementBundle信息傳遞給ActivityB resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElementBundle); notifyExitComplete(); } }); } } else { notifyExitComplete(); } } }
這里的notifyComplete
會(huì)在特定的條件下不斷觸發(fā),一旦isReadyToNotify
為true
,就會(huì)執(zhí)行方法里的邏輯
這里可能比較關(guān)心的是resultReceiver
到底是什么對(duì)象,是怎么賦值的???(這里在接下來(lái)講到ActivityB的時(shí)候會(huì)介紹到)
ActivityA
的流程暫時(shí)到這里就沒(méi)發(fā)走下去了
接下來(lái)我們來(lái)看看ActivityB
, 當(dāng)打開(kāi)了ActivityB
的時(shí)候會(huì)執(zhí)行Activity
的performStart
方法
// android.app.Activity源碼 final void performStart(String reason) { dispatchActivityPreStarted(); // getActivityOptions() 獲取到的是在上面ActivityA中創(chuàng)建的ActivityOptions對(duì)象 // 里面有支持的所有的共享元素名稱、ActivityA的ExitTransitionCoordinator對(duì)象、返回標(biāo)志等信息 mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason); ... mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); }
然后就進(jìn)入到ActivityTransitionState
的enterReady
方法
// android.app.ActivityTransitionState源碼 public void enterReady(Activity activity) { if (mEnterActivityOptions == null || mIsEnterTriggered) { return; } mIsEnterTriggered = true; mHasExited = false; // 獲取ActivityA傳過(guò)來(lái)的所有共享元素名稱 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); // 獲取ActivityA的ExitTransitionCoordinator ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); // 獲取返回標(biāo)志 final boolean isReturning = mEnterActivityOptions.isReturning(); if (isReturning) { restoreExitedViews(); activity.getWindow().getDecorView().setVisibility(View.VISIBLE); } // 創(chuàng)建EnterTransitionCoordinator,保存resultReceiver、sharedElementNames等對(duì)象 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(), mEnterActivityOptions.isCrossTask()); if (mEnterActivityOptions.isCrossTask()) { mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); } if (!mIsEnterPostponed) { // 是否推遲執(zhí)行動(dòng)畫,配合postponeEnterTransition方法使用 startEnter(); } }
上面的mIsEnterPostponed
,默認(rèn)值是false,可以通過(guò)postponeEnterTransition
和 startPostponedEnterTransition
控制什么時(shí)候執(zhí)行動(dòng)畫,這個(gè)不是重點(diǎn),我們忽略
我們先來(lái)看看EnterTransitionCoordinator
的構(gòu)造函數(shù)
// android.app.EnterTransitionCoordinator源碼 EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) { super(activity.getWindow(), sharedElementNames, getListener(activity, isReturning && !isCrossTask), isReturning); mActivity = activity; mIsCrossTask = isCrossTask; // 保存ActivityA的ExitTransitionCoordinator對(duì)象到mResultReceiver中 setResultReceiver(resultReceiver); // 這里會(huì)將ActivityB的window背景設(shè)置成透明 prepareEnter(); // 構(gòu)造resultReceiverBundle,保存EnterTransitionCoordinator對(duì)象 Bundle resultReceiverBundle = new Bundle(); resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); // 發(fā)送MSG_SET_REMOTE_RECEIVER消息,將EnterTransitionCoordinator對(duì)象傳遞給ActivityA mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); ... }
這個(gè)時(shí)候ActivityA
那邊就接收到了ActivityB
的EnterTransitionCoordinator
對(duì)象
接下來(lái)我門看看ActivityA
是怎么接收MSG_SET_REMOTE_RECEIVER
消息的
// android.app.ExitTransitionCoordinator 源碼 @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_SET_REMOTE_RECEIVER: stopCancel(); // 將`ActivityB`的`EnterTransitionCoordinator`對(duì)象保存到mResultReceiver對(duì)象中 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); if (mIsCanceled) { mResultReceiver.send(MSG_CANCEL, null); mResultReceiver = null; } else { //又會(huì)觸發(fā)notifyComplete(),這個(gè)時(shí)候isReadyToNotify就為true了,就會(huì)執(zhí)行notifyComplete里的代碼 notifyComplete(); } break; ... } }
這個(gè)時(shí)候ActivityA
的共享元素動(dòng)畫的核心邏輯就基本已經(jīng)走完了
接下來(lái)繼續(xù)看ActivityB
的邏輯,接來(lái)下會(huì)執(zhí)行startEnter
方法
// android.app.ActivityTransitionState源碼 private void startEnter() { if (mEnterTransitionCoordinator.isReturning()) { // 這個(gè)為false if (mExitingToView != null) { mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, mExitingToView); } else { mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); } } else { // 會(huì)執(zhí)行這個(gè)邏輯 mEnterTransitionCoordinator.namedViewsReady(null, null); mPendingExitNames = null; } mExitingFrom = null; mExitingTo = null; mExitingToView = null; mEnterActivityOptions = null; }
也就是說(shuō)接下來(lái)會(huì)觸發(fā)EnterTransitionCoordinator
的namedViewsReady
方法, 然后就會(huì)觸發(fā)viewsReady
方法
// android.app.EnterTransitionCoordinator源碼 public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { triggerViewsReady(mapNamedElements(accepted, localNames)); } // android.app.EnterTransitionCoordinator源碼 private void triggerViewsReady(final ArrayMap<String, View> sharedElements) { if (mAreViewsReady) { return; } mAreViewsReady = true; final ViewGroup decor = getDecor(); // Ensure the views have been laid out before capturing the views -- we need the epicenter. if (decor == null || (decor.isAttachedToWindow() && (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) { viewsReady(sharedElements); } else { mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> { mViewsReadyListener = null; // 觸發(fā)viewsReady viewsReady(sharedElements); }); decor.invalidate(); } }
EnterTransitionCoordinator
的viewsReady
代碼邏輯 跟 ExitTransitionCoordinator
的差不多,準(zhǔn)備好共享元素和非共享元素等,
// android.app.EnterTransitionCoordinator源碼 @Override protected void viewsReady(ArrayMap<String, View> sharedElements) { // 準(zhǔn)備好共享元素和非共享元素 super.viewsReady(sharedElements); mIsReadyForTransition = true; // 隱藏共享元素 hideViews(mSharedElements); Transition viewsTransition = getViewsTransition(); if (viewsTransition != null && mTransitioningViews != null) { // 將mTransitioningViews當(dāng)作target設(shè)置到viewsTransition中 removeExcludedViews(viewsTransition, mTransitioningViews); // 剔除掉mTransitioningViews中不在屏幕中的view stripOffscreenViews(); // 隱藏非共享元素 hideViews(mTransitioningViews); } if (mIsReturning) { sendSharedElementDestination(); } else { // 將共享元素用GhostView包裹,然后添加到ActivityB的decorView的OverlayView中 moveSharedElementsToOverlay(); } if (mSharedElementsBundle != null) { onTakeSharedElements(); } }
一般情況下,這個(gè)時(shí)候mSharedElementsBundle
為null,所以不會(huì)走onTakeSharedElements
方法
這里的mSharedElementsBundle
對(duì)象是在ActivityA的notifyComplete
中發(fā)送的MSG_TAKE_SHARED_ELEMENTS
消息傳過(guò)來(lái)的
// android.app.EnterTransitionCoordinator源碼 @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_TAKE_SHARED_ELEMENTS: if (!mIsCanceled) { mSharedElementsBundle = resultData; onTakeSharedElements(); } break; ... } }
可見(jiàn)當(dāng)ActivityB
接收到MSG_TAKE_SHARED_ELEMENTS
消息,賦值完mSharedElementsBundle
之后,也會(huì)執(zhí)行onTakeSharedElements
方法
接下來(lái)我們來(lái)看看onTakeSharedElements
方法
// android.app.EnterTransitionCoordinator源碼 private void onTakeSharedElements() { if (!mIsReadyForTransition || mSharedElementsBundle == null) { return; } final Bundle sharedElementState = mSharedElementsBundle; mSharedElementsBundle = null; OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() { @Override public void onSharedElementsReady() { final View decorView = getDecor(); if (decorView != null) { OneShotPreDrawListener.add(decorView, false, () -> { startTransition(() -> { startSharedElementTransition(sharedElementState); }); }); decorView.invalidate(); } } }; if (mListener == null) { listener.onSharedElementsReady(); } else { // 觸發(fā)SharedElementCallback.onSharedElementsArrived回調(diào) mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener); } }
接下來(lái)就會(huì)執(zhí)行startTransition
方法,然后執(zhí)行startSharedElementTransition
方法,開(kāi)始執(zhí)行ActivityB
的動(dòng)畫了
// android.app.EnterTransitionCoordinator源碼 private void startSharedElementTransition(Bundle sharedElementState) { ViewGroup decorView = getDecor(); if (decorView == null) { return; } // Remove rejected shared elements ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); // 過(guò)濾出ActivityA存在,ActivityB不存在的共享元素 rejectedNames.removeAll(mSharedElementNames); // 根據(jù)ActivityA傳過(guò)來(lái)的共享元素sharedElementState信息,創(chuàng)建快照view對(duì)象 // 這里會(huì)觸發(fā)SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); if (mListener != null) { // 觸發(fā)SharedElementCallback.onRejectSharedElements方法 mListener.onRejectSharedElements(rejectedSnapshots); } removeNullViews(rejectedSnapshots); // 執(zhí)行漸隱的退出動(dòng)畫 startRejectedAnimations(rejectedSnapshots); // 開(kāi)始創(chuàng)建共享元素的快照view // 這里會(huì)再觸發(fā)一遍SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); // 顯示共享元素 showViews(mSharedElements, true); // 添加OnPreDrawListener,在下一幀觸發(fā)SharedElementCallback.onSharedElementEnd回調(diào) scheduleSetSharedElementEnd(sharedElementSnapshots); // 設(shè)置共享元素設(shè)置到動(dòng)畫的開(kāi)始位置,并返回在ActivityB布局中的原始的狀態(tài)(即結(jié)束位置) // 這里會(huì)觸發(fā)SharedElementCallback.onSharedElementStart回調(diào) ArrayList<SharedElementOriginalState> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); pauseInput(); // 然后就開(kāi)始采集開(kāi)始幀和結(jié)束幀,執(zhí)行過(guò)度動(dòng)畫 Transition transition = beginTransition(decorView, startEnterTransition, startSharedElementTransition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (startEnterTransition) { // 添加監(jiān)聽(tīng)器,動(dòng)畫結(jié)束的時(shí)候,將window的背景恢復(fù)成不透明等 startEnterTransition(transition); } // 將共享元素設(shè)置到結(jié)束的位置(為了TransitionManager能采集到結(jié)束幀的值) setOriginalSharedElementState(mSharedElements, originalImageViewState); if (mResultReceiver != null) { // We can't trust that the view will disappear on the same frame that the shared // element appears here. Assure that we get at least 2 frames for double-buffering. decorView.postOnAnimation(new Runnable() { int mAnimations; @Override public void run() { if (mAnimations++ < MIN_ANIMATION_FRAMES) { View decorView = getDecor(); if (decorView != null) { decorView.postOnAnimation(this); } } else if (mResultReceiver != null) { mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); mResultReceiver = null; // all done sending messages. } } }); } }
接下來(lái)看一下beginTransition
方法
// android.app.EnterTransitionCoordinator源碼 private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition, boolean startSharedElementTransition) { Transition sharedElementTransition = null; if (startSharedElementTransition) { if (!mSharedElementNames.isEmpty()) { // 獲取共享元素的過(guò)渡動(dòng)畫類Transition,可以通過(guò)window.setSharedElementEnterTransition方法設(shè)置 // 一般不需要設(shè)置 有默認(rèn)值 sharedElementTransition = configureTransition(getSharedElementTransition(), false); } ... } Transition viewsTransition = null; if (startEnterTransition) { mIsViewsTransitionStarted = true; if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { // 獲取非共享元素的過(guò)渡動(dòng)畫類Transition,可以通過(guò)window.setEnterTransition方法設(shè)置 // 一般不需要設(shè)置 有默認(rèn)值 viewsTransition = configureTransition(getViewsTransition(), true); } ... // 合并成TransitionSet 對(duì)象 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); if (transition != null) { transition.addListener(new ContinueTransitionListener()); if (startEnterTransition) { setTransitioningViewsVisiblity(View.INVISIBLE, false); } // 開(kāi)始采集開(kāi)始幀和結(jié)束幀,執(zhí)行過(guò)度動(dòng)畫 TransitionManager.beginDelayedTransition(decorView, transition); if (startEnterTransition) { setTransitioningViewsVisiblity(View.VISIBLE, false); } decorView.invalidate(); } else { transitionStarted(); } return transition; }
到了這里,就會(huì)真正的開(kāi)始執(zhí)行 ActivityB
的共享元素和非共享元素的進(jìn)場(chǎng)動(dòng)畫
當(dāng)動(dòng)畫執(zhí)行結(jié)束之后就會(huì)觸發(fā) onTransitionsComplete
方法
// android.app.EnterTransitionCoordinator源碼 @Override protected void onTransitionsComplete() { // 將共享元素和GhostView從decorView的OverlayView中remove掉 moveSharedElementsFromOverlay(); final ViewGroup decorView = getDecor(); if (decorView != null) { decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); Window window = getWindow(); if (window != null && mReplacedBackground == decorView.getBackground()) { window.setBackgroundDrawable(null); } } if (mOnTransitionComplete != null) { mOnTransitionComplete.run(); mOnTransitionComplete = null; } }
用非常簡(jiǎn)單點(diǎn)的話總結(jié)共享元素流程是:
- ActivityA先執(zhí)行退場(chǎng)動(dòng)畫
- ActivityA將共享元素的結(jié)束位置等屬性傳遞給ActivityB
- ActivityB加載自己的布局,在onStart生命周期左右去找到共享元素 先定位到ActivityA的結(jié)束位置
- ActivityB開(kāi)始執(zhí)行過(guò)度動(dòng)畫,過(guò)渡到自己布局中的位置
這就是 從ActivityA打開(kāi)ActivityB的共享元素動(dòng)畫過(guò)程的核心源碼分析過(guò)程
ActivityB返回ActivityA
既然是返回,首先肯定是要調(diào)用ActivityB
的finishAfterTransition
方法
// android.app.Activity 源碼 public void finishAfterTransition() { if (!mActivityTransitionState.startExitBackTransition(this)) { finish(); } }
這里就會(huì)調(diào)用ActivityTransitionState
的startExitBackTransition
方法
// android.app.ActivityTransitionState源碼 public boolean startExitBackTransition(final Activity activity) { // 獲取打開(kāi)ActivityB時(shí) 傳過(guò)來(lái)的共享元素名稱 ArrayList<String> pendingExitNames = getPendingExitNames(); if (pendingExitNames == null || mCalledExitCoordinator != null) { return false; } else { if (!mHasExited) { mHasExited = true; Transition enterViewsTransition = null; ViewGroup decor = null; boolean delayExitBack = false; ... // 創(chuàng)建ExitTransitionCoordinator對(duì)象 mReturnExitCoordinator = new ExitTransitionCoordinator(activity, activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames, null, null, true); if (enterViewsTransition != null && decor != null) { enterViewsTransition.resume(decor); } if (delayExitBack && decor != null) { final ViewGroup finalDecor = decor; // 在下一幀調(diào)用startExit方法 OneShotPreDrawListener.add(decor, () -> { if (mReturnExitCoordinator != null) { mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); } }); } else { mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); } } return true; } }
這個(gè)方法首先會(huì)獲取到需要執(zhí)行退場(chǎng)動(dòng)畫的共享元素(由ActivityA
打開(kāi)ActivityB
時(shí)傳過(guò)來(lái)的),然后會(huì)創(chuàng)建ExitTransitionCoordinator
對(duì)象,最后調(diào)用startExit
執(zhí)行ActivityB
的退場(chǎng)邏輯
我們繼續(xù)看看ExitTransitionCoordinator
的構(gòu)造方法,雖然在上面在分析ActivityA
打開(kāi)ActivityB
時(shí)已經(jīng)看過(guò)了這個(gè)構(gòu)造方法,但ActivityB
返回ActivityA
時(shí)有點(diǎn)不一樣,accepted
和mapped
參數(shù)為null
,isReturning
參數(shù)為true
// android.app.ExitTransitionCoordinator源碼 public ExitTransitionCoordinator(Activity activity, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { super(window, names, listener, isReturning); // viewsReady方法跟上面介紹的一樣,主要是mapSharedElements不一樣了 viewsReady(mapSharedElements(accepted, mapped)); // 剔除掉mTransitioningViews中不在屏幕內(nèi)的非共享元素 stripOffscreenViews(); mIsBackgroundReady = !isReturning; mActivity = activity; } // android.app.ActivityTransitionCoordinator源碼 protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews) { ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); if (accepted != null) { for (int i = 0; i < accepted.size(); i++) { sharedElements.put(accepted.get(i), localViews.get(i)); } } else { ViewGroup decorView = getDecor(); if (decorView != null) { // 遍歷整個(gè)ActivityB的所有view,找到所有設(shè)置了transitionName屬性的view decorView.findNamedViews(sharedElements); } } return sharedElements; }
這里由于accepted
和mapped
參數(shù)為null
,所以會(huì)遍歷整個(gè)decorView
上的所有view
,找到所有設(shè)置了transitionName
屬性的view
,保存到sharedElements
然后viewsReady
就會(huì)根據(jù)自己所支持的共享元素名稱,從sharedElements
中刪除所有不支持的共享元素,然后對(duì)其排序,并保存到mSharedElements
(保存的view
對(duì)象)和mSharedElementNames
(保存的是共享元素名稱)中; 同時(shí)也會(huì)準(zhǔn)備好非共享元素view
對(duì)象,保存在mTransitioningViews
中
注意viewReady
會(huì)觸發(fā)SharedElementCallback.onMapSharedElements
回調(diào)
結(jié)下來(lái)就會(huì)執(zhí)行ExitTransitionCoordinator
的startExit
方法
// android.app.ExitTransitionCoordinator源碼 public void startExit(int resultCode, Intent data) { if (!mIsExitStarted) { ... // 這里又將ActivityB的共享元素用GhostView包裹一下,添加的decorView的OverlayView中 moveSharedElementsToOverlay(); // 將ActivityB的window背景設(shè)置成透明 if (decorView != null && decorView.getBackground() == null) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } final boolean targetsM = decorView == null || decorView.getContext() .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M; ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames : mAllSharedElementNames; //這里創(chuàng)建options對(duì)象,保存ExitTransitionCoordinator、sharedElementNames等對(duì)象 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, sharedElementNames, resultCode, data); // 這里會(huì)將ActivityB改成透明的activity,同時(shí)會(huì)將options對(duì)象傳給ActivityA mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { @Override public void onTranslucentConversionComplete(boolean drawComplete) { if (!mIsCanceled) { fadeOutBackground(); } } }, options); startTransition(new Runnable() { @Override public void run() { startExitTransition(); } }); } }
這個(gè)方法的主要作用是
- 使用
GhostView
將共享元素view
添加到最頂層decorView
的OverlayView
中 - 然后創(chuàng)建一個(gè)
ActivityOptions
對(duì)象,把ActivityB
的ExitTransitionCoordinator
對(duì)象和支持的共享元素集合對(duì)象傳遞給ActivityA
- 將ActivityB改成透明背景
然后就會(huì)執(zhí)行startExitTransition
方法
// android.app.ExitTransitionCoordinator源碼 private void startExitTransition() { // 獲取ActivityB的非共享元素退場(chǎng)的過(guò)渡動(dòng)畫Transition對(duì)象 // 最終會(huì)調(diào)用getReturnTransition方法獲取Transition對(duì)象 Transition transition = getExitTransition(); ViewGroup decorView = getDecor(); if (transition != null && decorView != null && mTransitioningViews != null) { setTransitioningViewsVisiblity(View.VISIBLE, false); // 開(kāi)始執(zhí)行非共享元素的退場(chǎng)動(dòng)畫 TransitionManager.beginDelayedTransition(decorView, transition); setTransitioningViewsVisiblity(View.INVISIBLE, false); decorView.invalidate(); } else { transitionStarted(); } }
看到這里我們就知道了,這里會(huì)單獨(dú)先執(zhí)行非共享元素的退場(chǎng)動(dòng)畫
ActivityB
的退場(chǎng)流程暫時(shí)就走到這里了,結(jié)下來(lái)就需要ActivityA
的配和,所以接下來(lái)我們來(lái)看看ActivityA
的進(jìn)場(chǎng)邏輯
ActivityA
進(jìn)場(chǎng)時(shí),會(huì)調(diào)用performStart
方法
// android.app.Activity 源碼 final void performStart(String reason) { dispatchActivityPreStarted(); // 這里的getActivityOptions()獲取到的就是上面說(shuō)的`ActivityB`傳過(guò)來(lái)的對(duì)象 mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason); ... // ActivityA準(zhǔn)備執(zhí)行進(jìn)場(chǎng)邏輯 mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); } // android.app.ActivityTransitionState 源碼 public void enterReady(Activity activity) { // mEnterActivityOptions對(duì)象就是`ActivityB`傳過(guò)來(lái)的對(duì)象 if (mEnterActivityOptions == null || mIsEnterTriggered) { return; } mIsEnterTriggered = true; mHasExited = false; // 共享元素名稱 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); // ActivityB的ExitTransitionCoordinator對(duì)象 ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); // 返回標(biāo)志 true final boolean isReturning = mEnterActivityOptions.isReturning(); if (isReturning) { restoreExitedViews(); activity.getWindow().getDecorView().setVisibility(View.VISIBLE); } // 創(chuàng)建ActivityA的EnterTransitionCoordinator對(duì)象 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(), mEnterActivityOptions.isCrossTask()); if (mEnterActivityOptions.isCrossTask()) { mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames()); } if (!mIsEnterPostponed) { startEnter(); } }
ActivityA
進(jìn)場(chǎng)時(shí),會(huì)在performStart
里獲取并保存ActivityB
傳過(guò)來(lái)的對(duì)象,然后創(chuàng)建EnterTransitionCoordinator
進(jìn)場(chǎng)動(dòng)畫實(shí)現(xiàn)的核心類,然后調(diào)用startEnter方法
// android.app.ActivityTransitionState 源碼 private void startEnter() { if (mEnterTransitionCoordinator.isReturning()) { // 這里的mExitingFrom、mExitingTo、mExitingToView是ActivityA打開(kāi)ActivityB的時(shí)候保存下載的對(duì)象 // 所以一般情況下都不為null if (mExitingToView != null) { mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, mExitingToView); } else { mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); } } else { mEnterTransitionCoordinator.namedViewsReady(null, null); mPendingExitNames = null; } mExitingFrom = null; mExitingTo = null; mExitingToView = null; mEnterActivityOptions = null; }
接下來(lái)就會(huì)執(zhí)行EnterTransitionCoordinator
的viewInstancesReady
方法
// android.app.EnterTransitionCoordinator 源碼 public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, ArrayList<View> localViews) { boolean remap = false; for (int i = 0; i < localViews.size(); i++) { View view = localViews.get(i); // view的TransitionName屬性有沒(méi)有發(fā)生變化,或者view對(duì)象沒(méi)有AttachedToWindow if (!TextUtils.equals(view.getTransitionName(), localNames.get(i)) || !view.isAttachedToWindow()) { remap = true; break; } } if (remap) {// 如果發(fā)生了變化,則會(huì)調(diào)用mapNamedElements遍歷decorView找到所有設(shè)置了TransitionName的view triggerViewsReady(mapNamedElements(accepted, localNames)); } else { // 一般會(huì)執(zhí)行這個(gè)else triggerViewsReady(mapSharedElements(accepted, localViews)); } }
接下來(lái)就會(huì)執(zhí)行 triggerViewsReady
,里面就會(huì)調(diào)用viewsReady
方法,viewsReady
在上面介紹過(guò),唯一有點(diǎn)不一樣的是 這里的mIsReturning
是true
, 所以會(huì)執(zhí)行sendSharedElementDestination
方法
// android.app.EnterTransitionCoordinator源碼 @Override protected void viewsReady(ArrayMap<String, View> sharedElements) { // 準(zhǔn)備好共享元素和非共享元素 super.viewsReady(sharedElements); mIsReadyForTransition = true; // 隱藏共享元素 hideViews(mSharedElements); Transition viewsTransition = getViewsTransition(); if (viewsTransition != null && mTransitioningViews != null) { // 將mTransitioningViews當(dāng)作target設(shè)置到viewsTransition中 removeExcludedViews(viewsTransition, mTransitioningViews); // 剔除掉mTransitioningViews中不在屏幕中的view stripOffscreenViews(); // 隱藏非共享元素 hideViews(mTransitioningViews); } if (mIsReturning) { sendSharedElementDestination(); } else { moveSharedElementsToOverlay(); } if (mSharedElementsBundle != null) { onTakeSharedElements(); } }
// android.app.EnterTransitionCoordinator源碼 private void sendSharedElementDestination() { boolean allReady; final View decorView = getDecor(); if (allowOverlappingTransitions() && getEnterViewsTransition() != null) { allReady = false; } else if (decorView == null) { allReady = true; } else { allReady = !decorView.isLayoutRequested(); if (allReady) { for (int i = 0; i < mSharedElements.size(); i++) { if (mSharedElements.get(i).isLayoutRequested()) { allReady = false; break; } } } } if (allReady) { // 捕獲共享元素當(dāng)前的狀態(tài), 會(huì)觸發(fā)SharedElementCallback.onCaptureSharedElementSnapshot方法 Bundle state = captureSharedElementState(); // 將共享元素view 添加的最頂層(decorView的OverlayView中) moveSharedElementsToOverlay(); // 給ActivityB發(fā)送MSG_SHARED_ELEMENT_DESTINATION,將共享元素的狀態(tài)傳給ActivityB mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } else if (decorView != null) { OneShotPreDrawListener.add(decorView, () -> { if (mResultReceiver != null) { Bundle state = captureSharedElementState(); moveSharedElementsToOverlay(); mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); } }); } if (allowOverlappingTransitions()) { // 執(zhí)行非共享元素的進(jìn)場(chǎng)動(dòng)畫 startEnterTransitionOnly(); } }
sendSharedElementDestination
方法主要有以下三個(gè)作用
- 獲取
ActivityA
當(dāng)前共享元素的狀態(tài) 傳給ActivityB
,當(dāng)作過(guò)度動(dòng)畫結(jié)束位置的狀態(tài)(即結(jié)束幀) - 將共享元素添加到最頂層(decorView的OverlayView中)
- 給
ActivityB
發(fā)送MSG_SHARED_ELEMENT_DESTINATION
消息傳遞state
- 優(yōu)先開(kāi)始執(zhí)行
ActivityA
的非共享元素的進(jìn)場(chǎng)動(dòng)畫
到這里ActivityA
的邏輯暫時(shí)告一段落,接下來(lái)我們來(lái)看看ActivityB
接收到MSG_SHARED_ELEMENT_DESTINATION
時(shí)干了些什么
// android.app.ExitTransitionCoordinator @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { ... case MSG_SHARED_ELEMENT_DESTINATION: // 保存ActivityA傳過(guò)來(lái)的共享元素狀態(tài) mExitSharedElementBundle = resultData; // 準(zhǔn)備執(zhí)行共享元素退出動(dòng)畫 sharedElementExitBack(); break; ... } }
接下來(lái)我們來(lái)看看sharedElementExitBack
方法
// android.app.ExitTransitionCoordinator private void sharedElementExitBack() { final ViewGroup decorView = getDecor(); if (decorView != null) { decorView.suppressLayout(true); } if (decorView != null && mExitSharedElementBundle != null && !mExitSharedElementBundle.isEmpty() && !mSharedElements.isEmpty() && getSharedElementTransition() != null) { startTransition(new Runnable() { public void run() { // 會(huì)執(zhí)行這個(gè)方法 startSharedElementExit(decorView); } }); } else { sharedElementTransitionComplete(); } }
接下來(lái)就會(huì)執(zhí)行startSharedElementExit
方法
// android.app.ExitTransitionCoordinator private void startSharedElementExit(final ViewGroup decorView) { // 獲取共享元素的過(guò)度動(dòng)畫的Transition對(duì)象,里面最終會(huì)調(diào)用`getSharedElementReturnTransition`方法獲取該對(duì)象 Transition transition = getSharedElementExitTransition(); transition.addListener(new TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); if (isViewsTransitionComplete()) { delayCancel(); } } }); // 根據(jù)ActivityA傳過(guò)來(lái)的狀態(tài),創(chuàng)建快照view對(duì)象 // 這里會(huì)觸發(fā)SharedElementCallback.onCreateSnapshotView方法 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames); OneShotPreDrawListener.add(decorView, () -> { // 在下一幀觸發(fā),將共享元素的屬性設(shè)置到開(kāi)始狀態(tài)(ActivityA中共享元素的狀態(tài)) // 這里會(huì)觸發(fā)SharedElementCallback.onSharedElementStart回調(diào) setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); }); setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); if (mListener != null) { // 先觸發(fā)SharedElementCallback.onSharedElementEnd回調(diào) mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots); } // 采集開(kāi)始幀和結(jié)束幀,并執(zhí)行動(dòng)畫 TransitionManager.beginDelayedTransition(decorView, transition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); decorView.invalidate(); }
看到上面的方法你可能會(huì)發(fā)現(xiàn),先觸發(fā)了onSharedElementEnd
方法,然后再觸發(fā)onSharedElementStart
,這可能是因?yàn)?code>ActivityB返回到ActivityA
時(shí), google
工程師定義為是從結(jié)束狀態(tài)返回到開(kāi)始狀態(tài)吧,即ActivityB
的狀態(tài)為結(jié)束狀態(tài),ActivityA
的狀態(tài)為開(kāi)始狀態(tài)
至于setGhostVisibility
和scheduleGhostVisibilityChange
主要的作用是為TransitionManager
采集開(kāi)始幀和結(jié)束幀執(zhí)行動(dòng)畫用的
到這里ActivityB
就開(kāi)始執(zhí)行共享元素的退出動(dòng)畫了
當(dāng)ActivityB
共享元素動(dòng)畫執(zhí)行結(jié)束之后,就會(huì)調(diào)用sharedElementTransitionComplete
方法,然后就會(huì)調(diào)用notifyComplete
方法
@Override protected void sharedElementTransitionComplete() { // 這里又會(huì)獲取ActivityB共享元素的狀態(tài)(之后會(huì)傳給ActivityA) // 可能會(huì)觸發(fā)ActivityB的SharedElementCallback.onCaptureSharedElementSnapshot回調(diào) mSharedElementBundle = mExitSharedElementBundle == null ? captureSharedElementState() : captureExitSharedElementsState(); super.sharedElementTransitionComplete(); }
這里為什么要再一次獲取ActivityB
的共享元素的狀態(tài),因?yàn)樾枰獋鹘oActivityA
, 然后ActivityA
再根據(jù)條件判斷 共享元素的狀態(tài)是否又發(fā)生了變化,然后交給ActivityA
自己去執(zhí)行共享元素動(dòng)畫
至于最后會(huì)執(zhí)行notifyComplete
,源碼就沒(méi)什么好看的了,上面也都介紹過(guò),這里面主要是給ActivityA
發(fā)送了MSG_TAKE_SHARED_ELEMENTS
消息,將ActivityB
的共享元素的狀態(tài)對(duì)象(mSharedElementBundle
)傳遞給ActivityA
到這里ActivityB
退場(chǎng)動(dòng)畫基本上就結(jié)束了,至于最后的狀態(tài)清空等處理 我們就不看了
接下來(lái)我們繼續(xù)看ActivityA
接收到MSG_TAKE_SHARED_ELEMENTS
消息后做了什么
@Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MSG_TAKE_SHARED_ELEMENTS: if (!mIsCanceled) { // 保存共享元素狀態(tài)對(duì)象 mSharedElementsBundle = resultData; // 準(zhǔn)備執(zhí)行共享元素動(dòng)畫 onTakeSharedElements(); } break; ... } }
結(jié)下來(lái)就會(huì)執(zhí)行onTakeSharedElements
方法,這些方法的源碼上面都介紹過(guò),我就不在貼出來(lái)了,這里面會(huì)觸發(fā)SharedElementCallback.onSharedElementsArrived
回調(diào),然后執(zhí)行startSharedElementTransition
// android.app.EnterTransitionCoordinator源碼 private void startSharedElementTransition(Bundle sharedElementState) { ViewGroup decorView = getDecor(); if (decorView == null) { return; } // Remove rejected shared elements ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); // 過(guò)濾出ActivityB存在,ActivityA不存在的共享元素 rejectedNames.removeAll(mSharedElementNames); // 根據(jù)ActivityB傳過(guò)來(lái)的共享元素sharedElementState信息,創(chuàng)建快照view對(duì)象 // 這里會(huì)觸發(fā)SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); if (mListener != null) { // 觸發(fā)SharedElementCallback.onRejectSharedElements方法 mListener.onRejectSharedElements(rejectedSnapshots); } removeNullViews(rejectedSnapshots); // 執(zhí)行漸隱的退出動(dòng)畫 startRejectedAnimations(rejectedSnapshots); // 開(kāi)始創(chuàng)建共享元素的快照view // 這里會(huì)再觸發(fā)一遍SharedElementCallback.onCreateSnapshotView方法 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); // 顯示共享元素 showViews(mSharedElements, true); // 添加OnPreDrawListener,在下一幀觸發(fā)SharedElementCallback.onSharedElementEnd回調(diào) scheduleSetSharedElementEnd(sharedElementSnapshots); // 設(shè)置共享元素設(shè)置到動(dòng)畫的開(kāi)始位置,并返回在ActivityA布局中的原始的狀態(tài)(即結(jié)束位置) // SharedElementCallback.onSharedElementStart回調(diào) ArrayList<SharedElementOriginalState> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; boolean startSharedElementTransition = true; setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); pauseInput(); // 然后就開(kāi)始采集開(kāi)始幀和結(jié)束幀,執(zhí)行過(guò)度動(dòng)畫 Transition transition = beginTransition(decorView, startEnterTransition, startSharedElementTransition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); if (startEnterTransition) {// 這里為false,不會(huì)執(zhí)行, 因?yàn)榉枪蚕碓貏?dòng)畫執(zhí)行單獨(dú)執(zhí)行了 startEnterTransition(transition); } // 將共享元素設(shè)置到結(jié)束的位置(為了TransitionManager能采集到結(jié)束幀的值) setOriginalSharedElementState(mSharedElements, originalImageViewState); if (mResultReceiver != null) { // We can't trust that the view will disappear on the same frame that the shared // element appears here. Assure that we get at least 2 frames for double-buffering. decorView.postOnAnimation(new Runnable() { int mAnimations; @Override public void run() { if (mAnimations++ < MIN_ANIMATION_FRAMES) { View decorView = getDecor(); if (decorView != null) { decorView.postOnAnimation(this); } } else if (mResultReceiver != null) { mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); mResultReceiver = null; // all done sending messages. } } }); } }
這里要特別說(shuō)明的是
- 這里沒(méi)有執(zhí)行
ActivityA
的非共享元素的進(jìn)場(chǎng)動(dòng)畫,因?yàn)樵谥耙呀?jīng)優(yōu)先調(diào)用了非共享元素的進(jìn)場(chǎng)動(dòng)畫 - 雖然這里調(diào)用了
ActivityA
的共享元素動(dòng)畫,但是基本上并不會(huì)創(chuàng)建動(dòng)畫對(duì)象去執(zhí)行,因?yàn)?code>ActivityB傳過(guò)來(lái)的狀態(tài) 跟ActivityA
當(dāng)前的狀態(tài)是一模一樣的,除非你在某種情況下并在執(zhí)行動(dòng)畫之前 強(qiáng)制改變ActivityA
的當(dāng)前狀態(tài);所以你所看到的共享元素的退場(chǎng)動(dòng)畫其實(shí)是ActivityB
的共享元素退場(chǎng)動(dòng)畫,而不是ActivityA
的
最后ActivityA
的共享元素動(dòng)畫結(jié)束之后 會(huì)就調(diào)用onTransitionsComplete
(不需要執(zhí)行動(dòng)畫,就會(huì)立馬觸發(fā)),將ActivityA
的共享元素view從從decorView的ViewGroupOverlay中remove掉
到這里由ActivityB
返回ActivityA
的退場(chǎng)動(dòng)畫到這里基本上就結(jié)束了,至于最后的cancel
等狀態(tài)清理就不介紹了
到這里我也用非常簡(jiǎn)單點(diǎn)的大白話總結(jié)一下ActivityB
返回ActivityA
的退場(chǎng)動(dòng)畫:
- 將
ActivityB
的window背景設(shè)置成透明, 并執(zhí)行非共享元素的退場(chǎng)動(dòng)畫 - 返回到ActivityA時(shí),將會(huì)執(zhí)行到performStart方法,并執(zhí)行非共享元素的進(jìn)場(chǎng)動(dòng)畫
ActivityB
接收到ActivityA
傳過(guò)來(lái)的共享元素狀態(tài),開(kāi)始執(zhí)行共享元素的退場(chǎng)動(dòng)畫ActivityA
接收到ActivityB
的共享元素狀態(tài),繼續(xù)執(zhí)行共享元素動(dòng)畫(但由于兩個(gè)狀態(tài)沒(méi)有變化,所以并不會(huì)執(zhí)行動(dòng)畫,會(huì)立馬直接動(dòng)畫結(jié)束的回調(diào))
SharedElementCallback回調(diào)總結(jié)
最后我們?cè)诳偨Y(jié)以下SharedElementCallback
回調(diào)的順序,因?yàn)槟阌锌赡軙?huì)自定義這個(gè)類 做一些特定的邏輯處理
當(dāng)是ActivityA打開(kāi)ActivityB時(shí)
ActivityA: ==Exit, onMapSharedElements ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityB: ==Enter, onMapSharedElements ActivityA: ==Exit, onSharedElementsArrived ActivityB: ==Enter, onSharedElementsArrived ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onRejectSharedElements ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onSharedElementStart ActivityB: ==Enter, onSharedElementEnd
當(dāng)是ActivityB返回到ActivityA時(shí)
ActivityB: ==Enter, onMapSharedElements ActivityA: ==Exit, onMapSharedElements ActivityA: ==Exit, onCaptureSharedElementSnapshot ActivityB: ==Enter, onCreateSnapshotView ActivityB: ==Enter, onSharedElementEnd ActivityB: ==Enter, onSharedElementStart ActivityB: ==Enter, onSharedElementsArrived ActivityA: ==Exit, onSharedElementsArrived ActivityA: ==Exit, onRejectSharedElements ActivityA: ==Exit, onCreateSnapshotView ActivityA: ==Exit, onSharedElementStart ActivityA: ==Exit, onSharedElementEnd
好了,到這里 我所要介紹的內(nèi)容已經(jīng)結(jié)束了,上面的源碼是針對(duì)Android30和Android31分析的(我在不同的時(shí)間段用不同的筆記本寫的,所以上面的源碼有的是Android30的源碼,有的是Android31的源碼)
最后再附上一張Activity共享元素動(dòng)畫的全程時(shí)序圖
以上就是Android Activity共享元素動(dòng)畫示例解析的詳細(xì)內(nèi)容,更多關(guān)于Android Activity共享元素動(dòng)畫的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android registerForActivityResult動(dòng)態(tài)申請(qǐng)權(quán)限案例詳解
- ActivityManagerService廣播并行發(fā)送與串行發(fā)送示例解析
- ActivityManagerService廣播注冊(cè)與發(fā)送示例解析
- ActivityManagerService之Service啟動(dòng)過(guò)程解析
- Android面向單Activity開(kāi)發(fā)示例解析
- Vue3源碼分析reactivity實(shí)現(xiàn)方法示例
- vue3源碼分析reactivity實(shí)現(xiàn)原理
- Android10 App啟動(dòng)Activity源碼分析
- Android?registerForActivityResult新用法實(shí)現(xiàn)兩個(gè)Activity間數(shù)據(jù)傳遞
相關(guān)文章
Android基礎(chǔ)之使用Fragment控制切換多個(gè)頁(yè)面
Android官方已經(jīng)提供了Fragment的各種使用的Demo例子,在我們SDK下面的API Demo里面就包含了Fragment的各種使用例子,需要看Demo的朋友,直接看API Demo那個(gè)程序就可以了,不用到處去找。里面分開(kāi)不同功能,實(shí)現(xiàn)了不同的類2013-07-07Android編寫簡(jiǎn)單的網(wǎng)絡(luò)爬蟲
網(wǎng)絡(luò)爬蟲是捜索引擎抓取系統(tǒng)的重要組成部分。爬蟲的主要目的是將互聯(lián)網(wǎng)上的網(wǎng)頁(yè)下載到本地形成一個(gè)或聯(lián)網(wǎng)內(nèi)容的鏡像備份。本文的主要內(nèi)容是講在Android中如何編寫簡(jiǎn)單的網(wǎng)絡(luò)爬蟲。2016-07-07詳細(xì)解讀Android系統(tǒng)中的application標(biāo)簽
這篇文章主要介紹了Android系統(tǒng)中的application標(biāo)簽,以application來(lái)聲明App是Android入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-04-04Android開(kāi)發(fā)經(jīng)驗(yàn)談:并發(fā)編程(線程與線程池)(推薦)
這篇文章主要介紹了Android開(kāi)發(fā)經(jīng)驗(yàn)談:并發(fā)編程(線程與線程池),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Android開(kāi)發(fā)之使用通知欄顯示提醒信息的方法
這篇文章主要介紹了Android開(kāi)發(fā)之使用通知欄顯示提醒信息的方法,涉及Notification的使用及通知欄信息設(shè)置技巧,需要的朋友可以參考下2016-01-01Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽(tīng)
這篇文章主要為大家詳細(xì)介紹了Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽(tīng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Android仿eleme點(diǎn)餐頁(yè)面二級(jí)聯(lián)動(dòng)列表
本站一直在點(diǎn)外賣,于是心血來(lái)潮就像仿餓了么做個(gè)站,接下來(lái)通過(guò)本文給大家介紹android 二級(jí)聯(lián)動(dòng)列表,仿eleme點(diǎn)餐頁(yè)面的相關(guān)資料,需要的朋友可以參考下2016-10-10Android開(kāi)發(fā)實(shí)現(xiàn)NFC刷卡讀取的兩種方式
這篇文章主要為大家詳細(xì)介紹了Android開(kāi)發(fā)中實(shí)現(xiàn)NFC刷卡讀取的兩種方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09Android自定義帶動(dòng)畫效果的圓形ProgressBar
這篇文章主要為大家詳細(xì)介紹了Android自定義帶動(dòng)畫效果的圓形ProgressBar,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05