Android?Activity共享元素動(dòng)畫示例解析
正文
所謂Activity共享元素動(dòng)畫,就是從ActivityA跳轉(zhuǎn)到ActivityB 通過控制某些元素(View)從ActivityA開始幀的位置跳轉(zhuǎn)到ActivityB 結(jié)束幀的位置,應(yīng)用過度動(dòng)畫
Activity的共享元素動(dòng)畫,其動(dòng)畫核心是使用的Transition記錄共享元素的開始幀、結(jié)束幀,然后使用TransitionManager過度動(dòng)畫管理類調(diào)用beginDelayedTransition方法 應(yīng)用過度動(dòng)畫
注意:Android5.0才開始支持共享元素動(dòng)畫
所以咱們先介紹一下TransitionManager的一些基礎(chǔ)知識(shí)
TransitionManager介紹
TransitionManager是 Android5.0開始提供的一個(gè)過渡動(dòng)畫管理類,功能非常強(qiáng)大;其可應(yīng)用在兩個(gè)Activity之間、Fragment之間、View之間應(yīng)用過渡動(dòng)畫
TransitionManager有兩個(gè)比較重要的類Scene(場(chǎng)景)和Transition(過渡) , 咱們先來介紹一下這兩個(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),使用過渡動(dòng)畫運(yùn)行到指定的場(chǎng)景
生成場(chǎng)景
生成場(chǎng)景有兩種方式; 一種是調(diào)用靜態(tài)方法通過布局生成 Scene.getSceneForLayout(sceneRoot, R.layout.scene_a, this),一種是直接通過構(gòu)造方法new Scene(sceneRoot, viewHierarchy)指定view對(duì)象生成
這兩種方式其實(shí)差不多,第一種通過布局生成的方式在使用的時(shí)候會(huì)自動(dòng)inflate加載布局生成view對(duì)象
用法比較簡(jiǎn)單;下面我們來看一下官方的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í)行效果如下:

通過上面的效果可以看出,切換的一瞬間會(huì)立馬變成指定場(chǎng)景的所有view(文案全都變了),只是應(yīng)用了開始幀的位置而已,然后慢慢過渡到結(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);
}
可見切換到指定場(chǎng)景,比如切換到場(chǎng)景b, 會(huì)remove掉場(chǎng)景a的所有view,然后添加場(chǎng)景b的所有view
其次;這種方式的兩種場(chǎng)景之間的切換動(dòng)畫;是通過id確定兩個(gè)view之間的對(duì)應(yīng)關(guān)系,從而確定view的開始幀和結(jié)束幀 來執(zhí)行過渡動(dòng)畫;如果沒有id對(duì)應(yīng)關(guān)系的view(即沒有開始幀或結(jié)束幀), 會(huì)執(zhí)行刪除動(dòng)畫(默認(rèn)是漸隱動(dòng)畫)或添加動(dòng)畫(默認(rèn)是漸顯動(dòng)畫)(看源碼也可以通過transtionName屬性來指定對(duì)應(yīng)關(guān)系)
其視圖匹配對(duì)應(yīng)關(guān)系的源碼如下:
// startValues 是開始幀所有對(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ì)象(可以通過改變view的屬性,使用過渡動(dòng)畫)
matchInstances(unmatchedStart, unmatchedEnd);
break;
case MATCH_NAME:
// 匹配transitionName屬性是否相同(activity之間就是通過transtionName來匹配的)
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;
}
}
// 添加沒有匹配到的對(duì)象
addUnmatched(unmatchedStart, unmatchedEnd);
}
可見試圖的匹配關(guān)系有很多種;可以根據(jù) 視圖對(duì)象本身、視圖的id、視圖的transitionName屬性等匹配對(duì)應(yīng)關(guān)系
定義場(chǎng)景比較簡(jiǎn)單,其實(shí)相對(duì)比較復(fù)雜的是Transition過度動(dòng)畫
缺點(diǎn):個(gè)人覺得通過創(chuàng)建不同Scene對(duì)象實(shí)現(xiàn)動(dòng)畫效果比較麻煩,需要?jiǎng)?chuàng)建多套布局,后期難以維護(hù);所以一般這種使用TransitionManager.go(bScene)方法指定Scene對(duì)象的方式基本不常用,一般都是使用TransitionManager.beginDelayedTransition()方法來實(shí)現(xiàn)過渡動(dòng)畫
下面我們來介紹Transition,并配合使用TransitionManager.beginDelayedTransition()方法實(shí)現(xiàn)動(dòng)畫效果
Transition(過渡)
顧名思義 Transition 是過渡的意思,里面定義了怎么 記錄開始幀的屬性、記錄結(jié)束幀的屬性、創(chuàng)建動(dòng)畫或執(zhí)行動(dòng)畫的邏輯
我們先看看Transition源碼里比較重要的幾個(gè)方法
// android.transition.Transition的源碼
public abstract class Transition implements Cloneable {
...
// 通過實(shí)現(xiàn)這個(gè)方法記錄view的開始幀的屬性
public abstract void captureStartValues(TransitionValues transitionValues);
// 通過實(shí)現(xiàn)這個(gè)方法記錄view的結(jié)束幀的屬性
public abstract void captureEndValues(TransitionValues transitionValues);
// 通過記錄的開始幀和結(jié)束幀的屬性,創(chuàng)建動(dòng)畫
// 默認(rèn)返回null,即沒有動(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 過渡動(dòng)畫的話,一般只需要重寫前三個(gè)方法即可
當(dāng)前系統(tǒng)也提供了一套完成的Transition過渡動(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));
}
}
可見AutoTransition包含了 淡出、位移、改變大小、淡入 等一組效果
下面我們來自定義一些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)畫過渡類,實(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è)置開始位置的x偏移量為100px(定義開始幀的屬性)
view1.translationX = 100f
view2.translationX = 100f
// 調(diào)用beginDelayedTransition 會(huì)立馬調(diào)用 Transition的captureStartValues方法記錄開始幀
// 同時(shí)會(huì)添加一個(gè)OnPreDrawListener, 在屏幕刷新的下一幀觸發(fā)onPreDraw() 方法,然后調(diào)用captureEndValues方法記錄結(jié)束幀,然后開始執(zhí)行動(dòng)畫
TransitionManager.beginDelayedTransition(binding.beginDelayRoot, TransitionSet().apply {
// 實(shí)現(xiàn)上下移動(dòng)(因?yàn)闆]有改變view的left屬性所以, 所以它沒有左右移動(dòng)效果)
addTransition(ChangeBounds())
// 通過translationX屬性實(shí)現(xiàn)左右移動(dòng)
addTransition(XYTranslation())
// 通過backgroundColor屬性改變背景顏色
addTransition(BackgroundColorTransition())
})
// 下面開始改變視圖的屬性(定義結(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ì)有些疑問,為什么上面將translationX設(shè)置成100之后,立馬又改成了0;這樣有什么意義嗎??
可見Transition的使用和自定義也比較簡(jiǎn)單,同時(shí)也能達(dá)到一些比較炫酷的效果
請(qǐng)注意,改變view的屬性并不會(huì)立馬重新繪制視圖,而是在屏幕的下一幀(60fps的話,就是16毫秒一幀)去繪制;而在繪制下一幀之前調(diào)用了TransitionManager.beginDelayedTransition()方法,里面會(huì)觸發(fā)XYTransition的captureStartValues方法記錄開始幀(記錄的translationX為100),同時(shí)TransitionManager會(huì)添加OnPreDrawListener, 在屏幕下一幀到來觸發(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,表示是開始還是結(jié)束,對(duì)應(yīng)會(huì)調(diào)用captureStartValues和captureEndValues 方法
transition.captureValues(sceneRoot, true);
}
...
}
我們來簡(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) {
// 記錄開始幀的屬性
captureStartValues(values);
} else {
// 記錄結(jié)束幀的屬性
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 緩存開始幀的屬性到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) {
// 記錄開始幀的屬性
captureStartValues(values);
} else {
// 記錄結(jié)束幀的屬性
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 緩存開始幀的屬性到mStartValues中
addViewValues(mStartValues, view, values);
} else {
// 緩存結(jié)束幀的屬性到mEndValues中
addViewValues(mEndValues, view, values);
}
}
} else {
// 沒有指定目標(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) {
// 記錄開始幀的屬性
captureStartValues(values);
} else {
// 記錄結(jié)束幀的屬性
captureEndValues(values);
}
values.targetedTransitions.add(this);
capturePropagationValues(values);
if (start) {
// 緩存開始幀的屬性到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);
}
}
}
可見sceneChangeSetup方法就會(huì)觸發(fā)Transition的captureStartValues 方法
接下來我們來看看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);
}
}
// 開始執(zhí)行動(dòng)畫
// 這里就會(huì)調(diào)用 Transition的createAnimator方法 和 runAnimators方法
mTransition.playTransition(mSceneRoot);
return true;
}
};
Transition的playTransition沒啥好看的,至此TransitionManager的beginDelayedTransition源碼分析到這里
上面源碼里你可能也看到了Transition可以設(shè)置目標(biāo)視圖,應(yīng)用過渡動(dòng)畫, 主要是通過addTarget方法實(shí)現(xiàn)的,如果沒有設(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從原來的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)單,我們來看看一個(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顯示不出來
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顯示不出來
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顯示不出來;如果添加的是drawable 需要手動(dòng)調(diào)用setBounds,不然drawable也顯示不出來
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在原來的parent中隱藏(即不繪制視圖)
mView.setTransitionVisibility(View.INVISIBLE);
parent.invalidate();
}
@Override
public void setVisibility(@Visibility int visibility) {
super.setVisibility(visibility);
if (mView.mGhostView == this) {
// 如果view在ghostview中繪制(可見),則設(shè)置在原來的parent不繪制(不可見)
// 如果view在ghostview中不繪制(不可見),則設(shè)置在原來的parent繪制(可見)
int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
mView.setTransitionVisibility(inverseVisibility);
}
}
}
看源碼得知 如果把View 添加到GhostView里,則默認(rèn)會(huì)調(diào)用view的setTransitionVisibility方法 將view設(shè)置成在parent中不可見, 在GhostView里可見;調(diào)用GhostView的setVisibility方法設(shè)置 要么在GhostView中可見,要么在parent中可見
系統(tǒng)內(nèi)部是使用GhostView.addGhost靜態(tài)方法添加GhostView的
我們來看看添加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;
}
可見內(nèi)部的實(shí)現(xiàn)最終將GhostView添加到了ViewGroupOverlay(遮罩層)里
配合GhostView,同時(shí)也解決了ViewGroupOverlay會(huì)將view從parent中remove的問題(即可同時(shí)在ViewGroupOverlay和原來的parent中繪制)
我們來看看一個(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)
}
}
// 我們無法直接使用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
}
}
效果圖如下:

可見使用GhostView并通過setVisibility方法,實(shí)現(xiàn)的效果是 既可以在window.decorView的ViewGroupOverlay中繪制,也可以在原來的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)備工作做完了之后,下面我們來真正的分析Activity的共享元素源碼
我們先以ActivityA打開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)的即可
下面我們來簡(jiǎn)單介紹下SharedElementCallback的一些回調(diào)在什么情況下觸發(fā)
public abstract class SharedElementCallback {
...
static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() {
};
/**
* 共享元素 開始幀準(zhǔn)備好了 觸發(fā)
* @param sharedElementNames 共享元素名稱
* @param sharedElements 共享元素view,并且已經(jīng)將開始幀的屬性應(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打開ActivityB時(shí)傳過來的所有共享元素名稱)
* @param sharedElements 需要做動(dòng)畫的共享元素名稱及view的對(duì)應(yīng)關(guān)系
* 注意:比如ActivityA打開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打開ActivityB, 則會(huì)把sharedElementBundle傳給ActivityB
**/
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
RectF screenBounds) {
...
}
/**
* 根據(jù)snapshot反過來創(chuàng)建view
* 如果是ActivityA打開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ā)(表明接下來可以準(zhǔn)備執(zhí)行過場(chǎng)動(dòng)畫了)
* 比如: ActivityA 打開 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)方法的大致意思是這樣的
接下來我門繼續(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í)的過場(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;
}
}
接下來我們來看看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打開ActivityB,則是makeSceneTransitionAnimation方法傳過來的共享元素名稱
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方法,核心作用就是我上面說的
// 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) {
// 開始處理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ì)使用到最開始介紹的GhostView和OverlayView ,目的是將共享元素繪制到最頂層
然后開始執(zhí)行beginTransitions方法
// android.app.ExitTransitionCoordinator源碼
private void beginTransitions() {
// 獲取共享元素的過渡動(dòng)畫類Transition,可以通過window.setSharedElementExitTransition方法設(shè)置
// 一般不需要設(shè)置 有默認(rèn)值
Transition sharedElementTransition = getSharedElementExitTransition();
// 獲取非共享元素的過渡動(dòng)畫類Transition,也可以通過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);
}
// 開始采集開始幀和結(jié)束幀,執(zhí)行過度動(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í)行過度動(dòng)畫,采集完成之后,會(huì)顯示GhostView,而隱藏原來parent里的共享元素view
上面的sharedElementTransition和viewsTransition都添加了監(jiān)聽器,在動(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ù),一開始就為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ì)象,是怎么賦值的???(這里在接下來講到ActivityB的時(shí)候會(huì)介紹到)
ActivityA的流程暫時(shí)到這里就沒發(fā)走下去了
接下來我們來看看ActivityB, 當(dāng)打開了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傳過來的所有共享元素名稱
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,可以通過postponeEnterTransition和 startPostponedEnterTransition控制什么時(shí)候執(zhí)行動(dòng)畫,這個(gè)不是重點(diǎn),我們忽略
我們先來看看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ì)象
接下來我門看看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)走完了
接下來繼續(xù)看ActivityB的邏輯,接來下會(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;
}
也就是說接下來會(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消息傳過來的
// android.app.EnterTransitionCoordinator源碼
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_TAKE_SHARED_ELEMENTS:
if (!mIsCanceled) {
mSharedElementsBundle = resultData;
onTakeSharedElements();
}
break;
...
}
}
可見當(dāng)ActivityB接收到MSG_TAKE_SHARED_ELEMENTS消息,賦值完mSharedElementsBundle之后,也會(huì)執(zhí)行onTakeSharedElements方法
接下來我們來看看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);
}
}
接下來就會(huì)執(zhí)行startTransition方法,然后執(zhí)行startSharedElementTransition方法,開始執(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);
// 過濾出ActivityA存在,ActivityB不存在的共享元素
rejectedNames.removeAll(mSharedElementNames);
// 根據(jù)ActivityA傳過來的共享元素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);
// 開始創(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)畫的開始位置,并返回在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();
// 然后就開始采集開始幀和結(jié)束幀,執(zhí)行過度動(dòng)畫
Transition transition = beginTransition(decorView, startEnterTransition,
startSharedElementTransition);
scheduleGhostVisibilityChange(View.VISIBLE);
setGhostVisibility(View.VISIBLE);
if (startEnterTransition) {
// 添加監(jiān)聽器,動(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.
}
}
});
}
}
接下來看一下beginTransition方法
// android.app.EnterTransitionCoordinator源碼
private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
boolean startSharedElementTransition) {
Transition sharedElementTransition = null;
if (startSharedElementTransition) {
if (!mSharedElementNames.isEmpty()) {
// 獲取共享元素的過渡動(dòng)畫類Transition,可以通過window.setSharedElementEnterTransition方法設(shè)置
// 一般不需要設(shè)置 有默認(rèn)值
sharedElementTransition = configureTransition(getSharedElementTransition(), false);
}
...
}
Transition viewsTransition = null;
if (startEnterTransition) {
mIsViewsTransitionStarted = true;
if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
// 獲取非共享元素的過渡動(dòng)畫類Transition,可以通過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);
}
// 開始采集開始幀和結(jié)束幀,執(zhí)行過度動(dòng)畫
TransitionManager.beginDelayedTransition(decorView, transition);
if (startEnterTransition) {
setTransitioningViewsVisiblity(View.VISIBLE, false);
}
decorView.invalidate();
} else {
transitionStarted();
}
return transition;
}
到了這里,就會(huì)真正的開始執(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開始執(zhí)行過度動(dòng)畫,過渡到自己布局中的位置
這就是 從ActivityA打開ActivityB的共享元素動(dòng)畫過程的核心源碼分析過程
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) {
// 獲取打開ActivityB時(shí) 傳過來的共享元素名稱
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打開ActivityB時(shí)傳過來的),然后會(huì)創(chuàng)建ExitTransitionCoordinator對(duì)象,最后調(diào)用startExit 執(zhí)行ActivityB的退場(chǎng)邏輯
我們繼續(xù)看看ExitTransitionCoordinator的構(gòu)造方法,雖然在上面在分析ActivityA打開ActivityB時(shí)已經(jīng)看過了這個(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é)下來就會(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)的過渡動(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);
// 開始執(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é)下來就需要ActivityA的配和,所以接下來我們來看看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()獲取到的就是上面說的`ActivityB`傳過來的對(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`傳過來的對(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傳過來的對(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打開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;
}
接下來就會(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屬性有沒有發(fā)生變化,或者view對(duì)象沒有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));
}
}
接下來就會(huì)執(zhí)行 triggerViewsReady,里面就會(huì)調(diào)用viewsReady方法,viewsReady在上面介紹過,唯一有點(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)作過度動(dòng)畫結(jié)束位置的狀態(tài)(即結(jié)束幀) - 將共享元素添加到最頂層(decorView的OverlayView中)
- 給
ActivityB發(fā)送MSG_SHARED_ELEMENT_DESTINATION消息傳遞state - 優(yōu)先開始執(zhí)行
ActivityA的非共享元素的進(jìn)場(chǎng)動(dòng)畫
到這里ActivityA的邏輯暫時(shí)告一段落,接下來我們來看看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傳過來的共享元素狀態(tài)
mExitSharedElementBundle = resultData;
// 準(zhǔn)備執(zhí)行共享元素退出動(dòng)畫
sharedElementExitBack();
break;
...
}
}
接下來我們來看看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();
}
}
接下來就會(huì)執(zhí)行startSharedElementExit方法
// android.app.ExitTransitionCoordinator
private void startSharedElementExit(final ViewGroup decorView) {
// 獲取共享元素的過度動(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傳過來的狀態(tài),創(chuàng)建快照view對(duì)象
// 這里會(huì)觸發(fā)SharedElementCallback.onCreateSnapshotView方法
final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
mSharedElementNames);
OneShotPreDrawListener.add(decorView, () -> {
// 在下一幀觸發(fā),將共享元素的屬性設(shè)置到開始狀態(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);
}
// 采集開始幀和結(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)返回到開始狀態(tài)吧,即ActivityB的狀態(tài)為結(jié)束狀態(tài),ActivityA的狀態(tài)為開始狀態(tài)
至于setGhostVisibility和scheduleGhostVisibilityChange主要的作用是為TransitionManager采集開始幀和結(jié)束幀執(zhí)行動(dòng)畫用的
到這里ActivityB就開始執(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,源碼就沒什么好看的了,上面也都介紹過,這里面主要是給ActivityA發(fā)送了MSG_TAKE_SHARED_ELEMENTS消息,將ActivityB的共享元素的狀態(tài)對(duì)象(mSharedElementBundle)傳遞給ActivityA
到這里ActivityB退場(chǎng)動(dòng)畫基本上就結(jié)束了,至于最后的狀態(tà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é)下來就會(huì)執(zhí)行onTakeSharedElements方法,這些方法的源碼上面都介紹過,我就不在貼出來了,這里面會(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);
// 過濾出ActivityB存在,ActivityA不存在的共享元素
rejectedNames.removeAll(mSharedElementNames);
// 根據(jù)ActivityB傳過來的共享元素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);
// 開始創(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)畫的開始位置,并返回在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();
// 然后就開始采集開始幀和結(jié)束幀,執(zhí)行過度動(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.
}
}
});
}
}
這里要特別說明的是
- 這里沒有執(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傳過來的狀態(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傳過來的共享元素狀態(tài),開始執(zhí)行共享元素的退場(chǎng)動(dòng)畫ActivityA接收到ActivityB的共享元素狀態(tài),繼續(xù)執(zhí)行共享元素動(dòng)畫(但由于兩個(gè)狀態(tà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打開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)過程解析
- Android面向單Activity開發(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è)頁面
Android官方已經(jīng)提供了Fragment的各種使用的Demo例子,在我們SDK下面的API Demo里面就包含了Fragment的各種使用例子,需要看Demo的朋友,直接看API Demo那個(gè)程序就可以了,不用到處去找。里面分開不同功能,實(shí)現(xiàn)了不同的類2013-07-07
Android編寫簡(jiǎn)單的網(wǎng)絡(luò)爬蟲
網(wǎng)絡(luò)爬蟲是捜索引擎抓取系統(tǒng)的重要組成部分。爬蟲的主要目的是將互聯(lián)網(wǎng)上的網(wǎng)頁下載到本地形成一個(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來聲明App是Android入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-04-04
Android開發(fā)經(jīng)驗(yàn)談:并發(fā)編程(線程與線程池)(推薦)
這篇文章主要介紹了Android開發(fā)經(jīng)驗(yàn)談:并發(fā)編程(線程與線程池),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽
這篇文章主要為大家詳細(xì)介紹了Android BroadcastReceiver實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)實(shí)時(shí)監(jiān)聽,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android仿eleme點(diǎn)餐頁面二級(jí)聯(lián)動(dòng)列表
本站一直在點(diǎn)外賣,于是心血來潮就像仿餓了么做個(gè)站,接下來通過本文給大家介紹android 二級(jí)聯(lián)動(dòng)列表,仿eleme點(diǎn)餐頁面的相關(guān)資料,需要的朋友可以參考下2016-10-10
Android開發(fā)實(shí)現(xiàn)NFC刷卡讀取的兩種方式
這篇文章主要為大家詳細(xì)介紹了Android開發(fā)中實(shí)現(xiàn)NFC刷卡讀取的兩種方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Android自定義帶動(dòng)畫效果的圓形ProgressBar
這篇文章主要為大家詳細(xì)介紹了Android自定義帶動(dòng)畫效果的圓形ProgressBar,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05

