1. 程式人生 > >viewpager佈局複用中FragmentPagerAdapter的坑,原始碼分析,控制元件id的一些思考

viewpager佈局複用中FragmentPagerAdapter的坑,原始碼分析,控制元件id的一些思考

一個fragment的佈局複用,裡面是tablayout+viewpager,viewpager載入不同adapter,adapter繼承FragmentPageAdapter。執行後有問題,先初始化的fragment正常顯示,後加載的fragment裡的viewpager全部是空白,這就很尷尬了,第一反應是fragment沒add進FragmentManager,因為在同一個activity裡,所以只有一個FragmentManager,debug一下。

List<Fragment> fragments = getSupportFragmentManager().getFragments();

果然第二個viewpager裡的fragment一個都沒add,繼續查問題。FragmentPageAdapter是通過getItem(position)獲取fragment物件的,並且已初始化過得fragment不會再次呼叫,會從FragmentManager中取出來,debug發現第二個getItem並未被呼叫,問題很明顯,FragmentPageAdapter認為對應position的fragment已經初始化過了,不重新呼叫,好吧,查原始碼吧。

 @Override
    public Object instantiateItem(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); 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); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } private static String makeFragmentName(int viewId, long id) { return "android:switcher:" + viewId + ":" + id; }

FragmentPageAdapter 繼承自PageAdapter,實現了instantiateItem方法,返回fragment物件。通過findFragmentByTag查詢fragment,如果存在就不呼叫getItem方法,於是關鍵就在於container.getId(),這個container其實就是我們的viewpager,列印viewpager.getId()

07-24 21:55:21.605 7796-7796/cf.movie.slmovie E/viewPage: movie>>>>>2131558543
07-24 21:55:21.640 7796-7796/cf.movie.slmovie E/viewPage: tv>>>>>2131558543

果然沒錯,id是一樣的,所以FragmentPageAdapter 不會重新呼叫getItem,這還沒完,怎麼辦呢,佈局copy一份,單獨用,還是沒用。好吧,viewpage的id改了一下,ok。
控制元件id的一些思考:
我們都知道每個view,layout系統都會分配一個id,儲存在R檔案裡,一一對應,使用的時候view.findViewById()去找對應的元件,這個id的生成機制沒找到。但是我們自己給view設定了一個id,專案龐大的時候,我們設定的這個id可能會有重複的,但是我們同一個view,同一個layout中的id不會重複,所以不用擔心重名的問題。
查詢R檔案發現同名的view只會生成一個id。

public static final int viewPage=0x7f0d0090;

回到本問題中,fragment新增的tag程式碼如下

"android:switcher:" + viewId + ":" + id;

viewId不同,才能標識不同的fragment,但是即使加上view.getParent()的id,依然無法保證他的唯一性,因為巢狀層數可能非常多。
不知道這個類設計的時候出於什麼考慮設計成這樣,應該留一個方法去處理設定tag比較好一點。
於是嘗試重寫instantiateItem方法,新增標識

 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        FragmentTransaction mCurTransaction = fm.beginTransaction();

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(which.toString(), container.getId(), itemId);
        Fragment fragment = fm.findFragmentByTag(name);
        if (fragment != null) {
            mCurTransaction.attach(fragment).commitAllowingStateLoss();
        } else {
            fragment = getItem(position);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(which.toString(), container.getId(), itemId)).commitAllowingStateLoss();
        }
        if (fragment != getItem(position - 1)) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }
        String tag = fragment.getTag();
        LogUtils.e("viewPager", "tag>>>>>" + tag);
        return fragment;
    }

    private static String makeFragmentName(String className, int viewId, long id) {
        return className + "android:switcher:" + viewId + ":" + id;
    }

事實證明,沒用,但是FragmentManager中有正確的fragment,但是就是不行,症狀一樣,繼續翻原始碼,只找到FragmentPageAdapter 中的一段註釋

When using FragmentPagerAdapter the host ViewPager must have a valid ID set.

可能對valid ID理解不同, 相同的ID不是有效的ID嗎,一定要unique?