一帽芽、場(chǎng)景
有時(shí)候我們會(huì)在一個(gè)頁(yè)面中添加多個(gè)ViewPager稿茉,有些場(chǎng)景可能由于特殊情況兩個(gè)ViewPager id相同颅夺,而這時(shí)候就會(huì)出現(xiàn)一種情況:只有第一個(gè)ViewPager正常展示箕宙,其余ViewPager不展示。
首先解釋下結(jié)果:
所有的Fragment都被添加到第一個(gè)ViewPager中逾苫,而且當(dāng)?shù)谝粋€(gè)ViewPager也是ViewPager+Fragment的情況下卿城,會(huì)出現(xiàn)相同position的Fragment只會(huì)被添加最先添加進(jìn)的一個(gè),F(xiàn)ragment的生命周期正常铅搓,且Fragment.getView().getParent()全都指向第一個(gè)ViewPager瑟押。
例如:
樣例1、
ViewPager A:ViewPager+Fragment形式星掰,添加了Fragment AF多望、BF。
ViewPager B:ViewPager+Fragment形式氢烘,添加了Fragment CF怀偷、DF。
ViewPager C:ViewPager+Fragment形式播玖,添加了Fragment EF椎工、FF,GF蜀踏。
這種場(chǎng)景下维蒙,只有AF、BF果覆、GF會(huì)被添加到ViewPager A中颅痊。ViewPager B和C都是無(wú)子Item。
樣例2随静、
ViewPager A:ViewPager+Fragment形式八千,添加了View A、B燎猛。
ViewPager B:ViewPager+Fragment形式,添加了Fragment CF照皆、DF重绷。
ViewPager C:ViewPager+Fragment形式,添加了Fragment EF膜毁、FF昭卓,GF愤钾。
這種場(chǎng)景下,View A候醒、B和CF能颁、DF、GF會(huì)被添加到ViewPager A中倒淫。ViewPager B和C都是無(wú)子Item伙菊。
二、原因分析
1敌土、首先需要看FragmentPagerAdapter的instantiateItem方法
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
#1 步驟
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
#2 步驟 這句是關(guān)鍵
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
public long getItemId(int position) {
return position;
}
方法中傳入的container對(duì)象即為ViewPager镜硕。
步驟1、代碼會(huì)根據(jù)生成的name從FragmentManager中找對(duì)應(yīng)的Fragment返干,如果找到兴枯,則不走getItem()獲取Fragment。如果我們的頁(yè)面中存在2個(gè)同名的Fragment而我們又沒(méi)有去修改makeFragmentName方法矩欠,就會(huì)產(chǎn)生2個(gè)ViewPager所生成的name值相同财剖,則此時(shí)第二個(gè)ViewPager通過(guò)FragmentManager.findFragmentByTag(name)方法找到的Fragment即不為空,此時(shí)不會(huì)執(zhí)行g(shù)etItem()方法癌淮,則第二個(gè)ViewPager對(duì)應(yīng)位置上的Fragment就不會(huì)被添加到FragmentManager中躺坟,進(jìn)而導(dǎo)致樣例1中的情況。
我們當(dāng)然可以通過(guò)重寫makeFragmentName()方法來(lái)使得name值唯一该默,但這依然會(huì)出現(xiàn)異常瞳氓,且看后面分析。
步驟2栓袖、Fragment會(huì)被添加到FragmentTransaction事務(wù)中匣摘。
接下來(lái)看FragmentTransaction中如何添加:
@NonNull
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
final Class<?> fragmentClass = fragment.getClass();
final int modifiers = fragmentClass.getModifiers();
...
if (containerViewId != 0) {
if (containerViewId == View.NO_ID) {
throw new IllegalArgumentException("Can't add fragment "
+ fragment + " with tag " + tag + " to container view with no id");
}
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
#步驟1
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
addOp(new Op(opcmd, fragment));
}
步驟1、這是我認(rèn)為最關(guān)鍵的一步裹刮,將Fragment的mContainerId指向?yàn)閏ontainerViewId音榜,而這個(gè)id便為我們ViewPager的id 。
接下來(lái)搜索Fragment.mContainerId在哪里使用:
#FragmentMangerImpl.java
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
case Fragment.CREATED:
...
if (newState > Fragment.CREATED) {
if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(
"Cannot create fragment "
+ f
+ " for a container view with no id"));
}
#步驟1
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
String resName;
try {
resName = f.getResources().getResourceName(f.mContainerId);
} catch (Resources.NotFoundException e) {
resName = "unknown";
}
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ resName
+ ") for fragment " + f));
}
}
#步驟2
f.mContainer = container;
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
#步驟3
container.addView(f.mView);
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
false);
// Only animate the view if it is visible. This is done after
// dispatchOnFragmentViewCreated in case visibility is changed
f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
&& f.mContainer != null;
} else {
f.mInnerView = null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
...
}
在FragmentManagerImpl.moveToState方法中捧弃,當(dāng)newState為CREATED的時(shí)候赠叼,演示了Fragment是如何添加到布局中的:
步驟1、通過(guò)mContainer.onFindViewById方法违霞,根據(jù)Fragment.mContainerId從頁(yè)面中找到Fragment需要添加的父布局嘴办。
mContainer是FragmentManagerImpl的局部變量,追溯onFindViewById方法最終會(huì)發(fā)現(xiàn)調(diào)用的是FragmentActivity.findViewById()或Fragment.mView.findViewById()买鸽,取決于該FragmentManager是在Activity下還是Fragment下涧郊。
也就是說(shuō),mContainer.onFindViewById方法最終會(huì)從根布局上找Fragment的父布局眼五,在該場(chǎng)景下妆艘,由于單個(gè)頁(yè)面中存在多個(gè)相同id的ViewPager彤灶,所以FragmentManagerImpl根據(jù)Fragment.mContainerId找到的一直都是第一個(gè)ViewPager。
步驟2批旺、調(diào)用Fragment.performCreateView()幌陕,在里面又會(huì)調(diào)用我們熟悉的onCreateView()方法創(chuàng)建根布局。
步驟3汽煮、container.addView()這里會(huì)把Fragment.mView添加到container里搏熄,這里也就證實(shí)了為什么后面ViewPager的Fragment會(huì)被添加進(jìn)第一個(gè)ViewPager里面了。
結(jié)論
當(dāng)同一個(gè)頁(yè)面下如果存在多個(gè)id相同的ViewPager時(shí)逗物,除了第一個(gè)ViewPager搬卒,后面的ViewPager如果添加的是Fragment時(shí)則會(huì)出現(xiàn)添加異常的情況,目標(biāo)Fragment由于父布局id相同翎卓,在FragmentMangerImpl中找父View時(shí)會(huì)找到第一個(gè)ViewPager契邀,從而出現(xiàn)所有的Fragment都添加到第一個(gè)ViewPager的情況,從而導(dǎo)致后面幾個(gè)ViewPager沒(méi)有內(nèi)容的情況失暴。
解決方案:改ViewPager的id就可以了坯门。
還有種情況是可能讓同一個(gè)頁(yè)面中允許存在2個(gè)相同id的ViewPager且顯示正常的。當(dāng)且僅當(dāng)兩個(gè)ViewPager傳入的FragmentManger不是同一個(gè)的情況(例如一個(gè)傳入的是Activity的getSupportFragmentManager逗扒,另一個(gè)傳入的是Fragment的getChildFragmentManager)古戴,當(dāng)FragmentManger不為同一個(gè)的情況時(shí),在步驟1中就會(huì)因?yàn)閺牟煌母季种袑ふ腋髯缘淖覸iew矩肩,這個(gè)時(shí)候找到的ViewPager就不會(huì)是同一個(gè)现恼,因而添加Fragment的顯示邏輯就會(huì)正常。