FragmentPagerAdapter使用及深思
2019-12-27 本文已影响0人
Magic旭
API熟悉
- private static String makeFragmentName(int viewId, long id)
该私有变量方法主要用于在定义fragmentManager如果寻找内存中已经存在的Fragment实例对象。
//其实这里可以自己定义自己的查询路径,下面会一一介绍
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
- public long getItemId(int position)
getItemId主要是提供给上面方法的id参数,这里系统默认的是用position来做id值。这里我们开发者可以直接覆盖系统值,返回一个我们fragment页面独特的id作为fragmentManager寻找内存中fragment的依据。
public long getItemId(int position) {
return (long)position;
}
//例如,这里我可以通过页面的id值作为查询路径的id
override fun getItemId(position: Int): Long {
return fragments[position].tag
}
- public Object instantiateItem(ViewGroup container, int position)
该方法的回调在ViewPager的addNewItem(int position, int index)里面被调用,主要就是ViewPager添加新的Item时候,会从adapter里面取对应position的ItemView。
//ViewPager不属于这篇文章的范畴
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//上面介绍到的方法2
final long itemId = getItemId(position);
//上面介绍到的方法1(假如方法2自己覆盖了id,也是可以通过自己的id参与的path的查找当中的)
String name = makeFragmentName(container.getId(), itemId);
//同一个Manager实例情况下,通过tag查找内存中的fragment实例
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
//假如通过tag找不到对应的fragment,则把回调返回给开发者,让开发者提供想要的fragment页面,这里在方法4介绍
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
//这里就属于setUserVisibleHint的回调了
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
FragmentCompat.setUserVisibleHint(fragment, false);
}
return fragment;
}
- public abstract Fragment getItem(int var1);
当FragmentManager中通过tag找不到对应的fragment时候,将提供新页面的方法回调给开发者,让开发者提供新页面。
FragmentManager如何存储Fragment
有兴趣可以自己到FragmentManager源码看下,这里不做详细讲解,源码里面储存和查找都是基于mAdds的List数组里面的。
//添加方法(还有onAttach方法等都会往mAdded集合里面添加fragment)
public void addFragment(Fragment fragment, boolean moveToStateNow) {
……
synchronized (mAdded) {
mAdded.add(fragment);
}
……
}
//查找
public Fragment findFragmentByTag(String tag) {
if (tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
……
return null;
}
实践
错误使用场景
- 场景是FragmentA+ViewPager嵌套2个FragmentB。当框架整体重建后,重新new了一个FragmentA,然后我的代码里面在onCreate方法里又重新创建的个List<Fragment>的数组。导致又创建了2个新的FragmentB添加到FragmentManagerImpl类里面的mAdds数组中了。所以当通过findFragmentByTag寻找某个fragment时候,因为是倒序查找的,自然找到了新的FragmentB。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
……
fragments.add(ChannelTitleMode(resources.getString(R.string.xxxx), FragmentB1()))
fragments.add(ChannelTitleMode(resources.getString(R.string.xxxx), FragmentB2()))
}
- 导致问题:因为FragmentPagerAdapter内部已经通过tag值找到了FragmentB1和FragmentB2,而且还不是旧的FragmentB1和FragmentB2。找到之后FragmentManager只是调用attach方法,不会用add方法,自然FragmentB1和FragmentB2的onAttach方法不会被调用。导致原来页面onAttach方法获取对应的对象引用为空,因为onAttach方法不被调用了。
详情请参考:FragmentManager各种方法对应的Fragment生命周期
正确使用场景
- FragmentPagerAdapter里面List数组储存的应该是<name,tag>,而不是<name,fragment>。这样可以通过tag查询到旧的fragment,里面的引用和变量不会改变。
const val CHANNEL_FRAGMENT_TAG = 1L
const val CATEGORY_FRAGMENT_TAG = 2L
class XXXXHomePagerAdapter(
//data class ChannelTitleModel(val title: String, val tag: Long)
//这里的数据结构很简单,就是用<name,tag>形式作为内容了,不再用<name,fragment>的形式
private val fragments: List<ChannelTitleModel>,
private val fm: FragmentManager,
private val pageId: Int
) : FragmentPagerAdapter(fm) {
override fun getPageTitle(position: Int): CharSequence? {
return fragments[position].title
}
override fun getItem(position: Int): Fragment {
return generateFragment(fragments[position].tag)
}
override fun getCount(): Int {
return fragments.size
}
private fun generateFragment(tag: Long): Fragment {
return when (tag) {
CHANNEL_FRAGMENT_TAG -> XXXXPageFragment()
else -> XXXXXFragment()
}
}
fun getTagFragment(position: Int): Fragment? {
val tag = fragments[position].tag
return fm.findFragmentByTag(makeFragmentName(pageId, tag))
}
//这一步比较关键,要重写源码的position作为id,用我们自身定义的tag作为查找的id
override fun getItemId(position: Int): Long {
return fragments[position].tag
}
private fun makeFragmentName(viewId: Int, id: Long): String {
return "android:switcher:$viewId:$id"
}
}
深思
- 为什么出现这种情况,说到底还是自己当初大学接触android时候没看源码,没养成看源码的习惯。导致大学中如果使用FragmentPagerAdapter的习惯保留到工作来了。(还是自己菜逼)
- 使用FragmentPagerAdapter的时候,千万别在你的Fragment的时候重新new Fragment,然后又塞给adapter,这样子是浪费内存资源的错误行为。
- 在Fragment+ViewPager嵌套多个fragment的时候,你的fragmentManager应该是childFragmentManager。
温馨补充:1、getFragmentManager belong to Activity;2、getChildFragmentManager belong to Fragment
例如:TO display Fragment1 on MainActivity we must use getSupportFragmentManager()
TO display Fragment2 from Fragment1 we have 2 way USE getFragmentManager()