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?