美文网首页
FragmentPagerAdapter使用及深思

FragmentPagerAdapter使用及深思

作者: Magic旭 | 来源:发表于2019-12-27 10:43 被阅读0次

    API熟悉

    1. private static String makeFragmentName(int viewId, long id)
      该私有变量方法主要用于在定义fragmentManager如果寻找内存中已经存在的Fragment实例对象。
    //其实这里可以自己定义自己的查询路径,下面会一一介绍
    private static String makeFragmentName(int viewId, long id) {
            return "android:switcher:" + viewId + ":" + id;
    }
    
    
    1. 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
    }
    
    1. 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;
        }
    
    1. 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;
        }
    

    实践

    错误使用场景
    1. 场景是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()))
            
        }
    
    1. 导致问题:因为FragmentPagerAdapter内部已经通过tag值找到了FragmentB1和FragmentB2,而且还不是旧的FragmentB1和FragmentB2。找到之后FragmentManager只是调用attach方法,不会用add方法,自然FragmentB1和FragmentB2的onAttach方法不会被调用。导致原来页面onAttach方法获取对应的对象引用为空,因为onAttach方法不被调用了。
      详情请参考:FragmentManager各种方法对应的Fragment生命周期
    正确使用场景
    1. 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"
        }
    }
    

    深思

    1. 为什么出现这种情况,说到底还是自己当初大学接触android时候没看源码,没养成看源码的习惯。导致大学中如果使用FragmentPagerAdapter的习惯保留到工作来了。(还是自己菜逼)
    2. 使用FragmentPagerAdapter的时候,千万别在你的Fragment的时候重新new Fragment,然后又塞给adapter,这样子是浪费内存资源的错误行为。
    3. 在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()

    相关文章

      网友评论

          本文标题:FragmentPagerAdapter使用及深思

          本文链接:https://www.haomeiwen.com/subject/pdqcoctx.html