多個(gè)ViewPager出現(xiàn)相同id場(chǎng)景

一帽芽、場(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ì)正常。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黍檩,一起剝皮案震驚了整個(gè)濱河市叉袍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刽酱,老刑警劉巖喳逛,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異棵里,居然都是意外死亡润文,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門殿怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)典蝌,“玉大人,你說(shuō)我怎么就攤上這事头谜≡ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵乔夯,是天一觀的道長(zhǎng)砖织。 經(jīng)常有香客問(wèn)我,道長(zhǎng)末荐,這世上最難降的妖魔是什么侧纯? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮甲脏,結(jié)果婚禮上眶熬,老公的妹妹穿的比我還像新娘。我一直安慰自己块请,他們只是感情好娜氏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著墩新,像睡著了一般贸弥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上海渊,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天绵疲,我揣著相機(jī)與錄音,去河邊找鬼臣疑。 笑死盔憨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讯沈。 我是一名探鬼主播郁岩,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缺狠!你這毒婦竟也來(lái)了问慎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儒老,失蹤者是張志新(化名)和其女友劉穎蝴乔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驮樊,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薇正,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囚衔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挖腰。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖练湿,靈堂內(nèi)的尸體忽然破棺而出猴仑,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布辽俗,位于F島的核電站疾渣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崖飘。R本人自食惡果不足惜榴捡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朱浴。 院中可真熱鬧吊圾,春花似錦、人聲如沸翰蠢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梁沧。三九已至檀何,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趁尼,已是汗流浹背埃碱。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酥泞,地道東北人砚殿。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芝囤,于是被迫代替她去往敵國(guó)和親似炎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348