FragmentPagerAdapter&FragmentStatePageAdapter整理

Posted microhex

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FragmentPagerAdapter&FragmentStatePageAdapter整理相关的知识,希望对你有一定的参考价值。

整理这个是因为平时用得很多,很多时候只是知其然,不知其所以然,现在有些时间,就慢慢撸一撸。

PagerAdapter

与ViewPager结合,现在应该是个app都会有ViewPager+PagerAdapter的组合了。PagerAdapter负责数据,而ViewPager负责展示。PagerAdapter是一个抽象类,FragmentPagerAdapter和FragmentStatePagerAdapter都是继承自PagerAdapter。当然也可以自己继承PagerAdapter:

public class MyPagerAdapter extends PagerAdapter 

    //PagerAdapter中 该方法直接throw exception 所以需要子类重写
    @Override
    public Object instantiateItem(ViewGroup container, int position) 
        return super.instantiateItem(container, position);
    

    //PagerAdapter中 该方法直接throw exception 所以需要子类重写
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) 
        super.destroyItem(container, position, object);
    

    @Override
    public int getCount() 
        return 0;
    

    @Override
    public boolean isViewFromObject(View view, Object object) 
        return false;
    

instantiateItem:
在每次ViewPager需要一个显示的Object的时候,该函数都会被ViewPager.addNewItem()调用,什么意思呢?说简单点就是ViewPager需要显示的内容,这个方法对于FragmentPagerAdapter和FragmentStatePagerAdapter是有重要区别的,稍后会扯到。

destroyItem
定义移除给定位置的Object对象的方法,pagerAdapter中没有实现,子类中需要给出实现

isViewFromObject
很多时候,都是一句话:

    @Override
    public boolean isViewFromObject(View view, Object object) 
        return object== view;
    

这里的object 是来自instantiateItem中返回的,判断当前显示的view是否关该object,个人感觉这个方法应该是与view获取焦点有关。

FragmentPagerAdapter

它继承PagerAdapter,通过名字,我们知道它专注于任意页为Fragment的情况。通过FragmentPagerAdapter文档所述,该类中每一个生成的Fragment都将保存在内存中,所以缺点非常明显,对于存在相对较多的fragment,程序将会吃掉非常多的内容。所以FragmentPagerAdapter适合那些相数量相对较少,静态的页面。对于存在多个fragment的情况,一般推荐使用FragmentStatePagerAdapter。FragmentPagerAdapter重载了几个必须实现的函数:

getItem()
不是继承自PagerAdapter,是FragmentPagerAdapter自身的一个函数,目的是生成我们需要的fragment。该方法会被FragmentPagerAdapter.instantiateItem()方法调用:

  @Override
  public Fragment getItem(int position) 
            Fragment fragment = new Fragment();
            Bundle bundle = new Bundle();
            bundle.putString("position", "" + position);
            fragment.setArguments(bundle);
            return fragment;
        

destoryItem()
该函数被调用后,会对Fragment进行FragmentTransaction.detach(),它并不是remove,而是detach[解除附着]了,因此fragment依旧在FragmentManager的管理中,Fragment依旧会占有资源。

instantiateItem()
它首先会判断一下要生成的Fragment是否已经存在[因为FragmentPagerAdapter通过FragmentManager保留所有已经生成的fragment],如果存在,那么使用旧的fragment,旧的fragment将会被attach;如果不存在,就调用getItem()生成一个新的,新的对象将会被保存,并FragmentTransation.add().举个例子:

public class CustomFragment extends Fragment 

    public static final String BUNDLE_KEY = CustomFragment.class.getSimpleName();

    private String mKey;

    private List<String> mList = new ArrayList<>();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
    

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) 
        View rootView = inflater.inflate(R.layout.fragment_layout, null);
        TextView tv = (TextView) rootView.findViewById(R.id.id_tv_content);
        tv.setText(getContent());

        return rootView;
    

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
        init();
    

    private void init() 
        for (int i = 0; i < 2000; i++) 
            mList.add(String.valueOf(i));
        

        Log.d(BUNDLE_KEY, "---" + mKey + "------>>" + mList.size());
    

    private String getContent() 
        Bundle bundle = getArguments();
        mKey = bundle.getString(BUNDLE_KEY);

        Log.d(BUNDLE_KEY, "---->>create view--->>" + mKey);

        return TextUtils.isEmpty(mKey) ? "content" : "position" + mKey;
    

    @Override
    public void onDestroyView() 
        super.onDestroyView();
        Log.d(BUNDLE_KEY, "onDestroyView ---->>" + mKey);
    

    @Override
    public void onDestroy() 
        super.onDestroy();
        Log.d(BUNDLE_KEY, "onDestroy ---->>" + mKey);
    

    @Override
    public void onDetach() 
        super.onDetach();
        Log.d(BUNDLE_KEY, "onDetach --->>>" + mKey);
    

主要看list.size():

private void init() 
        for (int i = 0; i < 2000; i++) 
            mList.add(String.valueOf(i));
        

        Log.d(BUNDLE_KEY, "---" + mKey + "------>>" + mList.size());
    

activity中方法:


    ViewPager mViewPager;

    List<String> mListContent = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 30; i++) 
            mListContent.add(String.valueOf(i));
        

        mViewPager = (ViewPager) findViewById(R.id.id_view_pager);
        mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager()));
    


    private class MyFragmentPagerAdapter extends FragmentPagerAdapter 

        public MyFragmentPagerAdapter(FragmentManager fm) 
            super(fm);
        

        @Override
        public Fragment getItem(int position) 
            CustomFragment fragment = new CustomFragment();
            Bundle bundle = new Bundle();
            bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
            fragment.setArguments(bundle);
            return fragment;
        

        @Override
        public int getCount() 
            return mListContent.size();
        
    

检查我们的log文件:

会发现list大小变成了原来的两倍,说明fragment没有被销毁,只有Fragment的视图被destroyView,而是被存储起来了,再次获取时,mlist并没有重新new,而是直接使用存储起来的。

FragmentStatePagerAdapter

与FragmentPagerAdapter一致,是继承自PagerAdapter,但是正如”State”所表明的含义一样,该PagerAdapter的实现将只保留当前页面,当页面离开时,就会被消除,释放其资源。在页面需要显示时,生成新的页面。这样实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存

getItem()
是FragmentStatePagerAdapter自己的方法,目的与FragmentStateAdapter中getItem()一致,生成需要的Fragment

instantiateItem()
此函数会调用getItem()函数,返回新的函数,新的对象将会被FragmentTransaction.add().FragmentStatePagerAdapter就是通过这种方式,每一次都会创建新的Fragment,而在不需要的情况下立刻释放资源,来达到节省内存的目的

destroyItem()
将Fragment移除,此时使用的FragementTransaction.remove(),并释放其资源:

   private class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter 

        public MyFragmentStatePagerAdapter(FragmentManager fm) 
            super(fm);
        

        @Override
        public Fragment getItem(int position) 
            CustomFragment fragment = new CustomFragment();
            Bundle bundle = new Bundle();
            bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
            fragment.setArguments(bundle);
            return fragment;
        

        @Override
        public int getCount() 
            return mListContent.size();
        
    

MainActivity:

mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getSupportFragmentManager()));

通过log可以看出:

最后

有的时候,我们不希望维持Fragment的状态,因为现在有很多动态的tab,将会存在多个fragment的情况,此时我们不能使用FragmentPagerAdapter,因为会占有大量的内存;使用FragmentStatePagerAdapter也不好,通过源码分析会发现FragmentPagerAdapter内部是通过两个集合来保存加入的Fragment和状态的:

因此,我们可以尝试自定义自己的PagerAdapter,不保存Fragment的状态,也不存储所有的fragment:

public abstract class CustomFragmentPagerAdapter extends PagerAdapter 

    private static final String TAG = CustomFragmentPagerAdapter.class.getSimpleName();

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction;
    private Fragment mCurrentPrimaryItem;

    public CustomFragmentPagerAdapter(FragmentManager fm) 
        mFragmentManager = fm;
    

    public abstract Fragment getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) 
        if (container.getId() == View.NO_ID) 
            throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id");
        
    

    @Override
    public Object instantiateItem(ViewGroup container, int position) 
        if (mCurTransaction == null) 
            mCurTransaction = mFragmentManager.beginTransaction();
        

        Fragment fragment = getItem(position);
        Log.i(TAG, "Adding fragment item #" + position + ": f=" + fragment);
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), getItemId(position)));
        return fragment;
    

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) 
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) 
            mCurTransaction = mFragmentManager.beginTransaction();
        
        Log.i(TAG, "Removing fragment #" + position + ": f=" + fragment + " v=" + fragment.getView());
        mCurTransaction.remove(fragment);
    

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) 
        Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) 
            if (mCurrentPrimaryItem != null) 
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            
            if (fragment != null) 
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            
            mCurrentPrimaryItem = fragment;
        
    

    public void commitUpdate() 
        if (mCurTransaction != null) 
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        
    

    @Override
    public boolean isViewFromObject(View view, Object object) 
        return ((Fragment) object).getView() == view;
    

    protected String makeFragmentName(int viewId, long id) 
        return "android:switcher:" + viewId + ":" + id;
    

    protected long getItemId(int position) 
        return position;
    

使用是:

public class MainActivity extends AppCompatActivity 

    ViewPager mViewPager;
    List<String> mListContent = new ArrayList<>();

    Fragment mCurrentFragment;
    CustomFragmentPagerAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 30; i++) 
            mListContent.add(String.valueOf(i));
        

        mViewPager = (ViewPager) findViewById(R.id.id_view_pager);
        mViewPager.setAdapter(mAdapter = new CustomFragmentPagerAdapter(getSupportFragmentManager()) 
            @Override
            public int getCount() 
                return mListContent.size();
            

            @Override
            public Fragment getItem(int position) 
                CustomFragment fragment = new CustomFragment();
                Bundle bundle = new Bundle();
                bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
                fragment.setArguments(bundle);
                return fragment;
            

            @Override
            public void setPrimaryItem(ViewGroup container, int position, Object object) 
                super.setPrimaryItem(container, position, object);
                if (mCurrentFragment == null) 
                    commitUpdate();
                
                mCurrentFragment = (Fragment) object;
            

            @Override
            public int getItemPosition(Object object) 
                return PagerAdapter.POSITION_NONE;
            
        );

        mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() 
            @Override
            public void onPageScrollStateChanged(int state) 
                super.onPageScrollStateChanged(state);
                //在viewPager状态停止时,我们才提交transcription
                if (state == ViewPager.SCROLL_STATE_IDLE)   
                    mAdapter.commitUpdate();
                
            
        );
    

更新于2018/06/28 19:49

最近在业务中遇到一个问题:在ViewPager+FragmentPagerAdapter中,可能需要增加\\删除,更新数据。类似于ListView,RecyclerView中Adapter直接使用notifyDataSetChanged()来刷新数据,但是没什么卵用。
基于这个问题,百度和google之后,很多人建议将FragmentPagerAdapter直接替换成FragmentStatePagerAdapter,并将FragmentStatePagerAdapter.getItemPosition方法改成POSITION_NONE返回值:

@Override
public int getItemPosition(Object object) 
       return PagerAdapter.POSITION_NONE;

基于这个说法,我也尝试了,的确能解决问题。但是现在的问题是,如果我非要使用FragmentPagerAdapter那该怎么做呢?我们主要分析FragmentPagerAdapter&FragmentStatePageAdapter不同点了,FPA(FragmentPagerAdapter)会生成的所有Fragment都保存在内存中,即使destoryItem方法也只是FragmentManager.detach这个Fragment,而不是销毁它;而FSPA(FragmentPagerStateAdapter)只专注于当前Fragment页面,离开视线的Fragment都将被remove掉,即从内存中去掉。

对于FPA想要notifyDataHasChanged起作用,需要重写getItem()和instantiateItem()方法,getItem是产出新的Fragment,而instantiateItem是通过FragmentManager管理获取存储的Fragment,此时的Fragment我们可以看成是old Fragment,虽然是从内存中获取的,但还是会执行onCreateView(),onActivityCreate()等等生命周期方法,但是此时不能使用getArguments()方法重新获取参数,因为你重新setArguments()时会报java.lang.IllegalStateException: Fragment already active异常,此时我们可以使用set方法将我们的参数设置进去,然后再执行相关操作。

FPA.notifyDataHasChanged()方法最终都会调用instantiateItem(), 所以我们设置参数时,先要获取父类的instantiateItem()获取Fragment,然后再设置相关参数,伪代码如下:

public class MyFragmentPagerAdapter extends FragmentPagerAdapter 
  @Override
  public Object instantiateItem(ViewGroup container, int position) 
      MyFragment myFragment = (MyFragment)super.instantiateItem(container, position);
      //相应动作
      myFragment.setValue("newValue");
      return myFragment;
  

  @Overrider
  public Fragment getItem(int position)
      MyFragment myFragment = new MyFragment();
      Bundle bundle = new Bundle();
      myFragment.setAgrument(bundle);
      return myFragment;
        

    @Override
    public int getItemPosition(Object object) 
           return PagerAdapter.POSITION_NONE;
    

当然,还需要设置getItemPosition方法返回值为PagerAdapter.POSITION_NONE,这里是来自FragmentPagerAdapter.notifyDataHasChanged():

public void notifyDataSetChanged() 
        synchronized (this) 
            if (mViewPagerObserver != null) 
                mViewPagerObserver.onChanged();
            
        
        mObservable.notifyChanged();
    

这里的mViewPagerObserver来自ViewPager的内部类:PagerObserver中解析:

void dataSetChanged() 
        //代码省略
        final int adapterCount = mAdapter.getCount();
        for (int i = 0; i < mItems.size(); i++) 
            final ItemInfo ii = mItems.get(i);
            final int newPos = mAdapter.getItemPosition(ii.object);

            //如果没有更改,数据将不会更新
            if (newPos == PagerAdapter.POSITION_UNCHANGED) 
                continue;
            

            //代码省略
    

所以个人的建议是,如果你的ViewPager关联的Fragment数目不多,且只存在数据的更新,而不存在数目的更新,推荐你使用FragmentPagerAdapter,但是如果你的ViewPager关联的Fragment数目较多,而且还存在动态的增加和删除,推荐你使用FragmentStatePagerAdapter,因为你只需要管理当前可见的Fragment即可,而相对于FragmentPagerAdapter,要想从内存里面清除,或者替换,或者增加新的Fragment,还是比较困难的,当然了,你可以自定义PagerAdapter,管理你想要的Fragment.

参考资料:
http://www.cnblogs.com/lianghui66/p/3607091.html
http://blog.csdn.net/hknock/article/details/46741573
http://blog.csdn.net/jackrex/article/details/9885469
https://blog.csdn.net/dyllove98/article/details/8806576

以上是关于FragmentPagerAdapter&FragmentStatePageAdapter整理的主要内容,如果未能解决你的问题,请参考以下文章

使用 FragmentPagerAdapter 的 FragmentTransaction

无法更新FragmentPagerAdapter中的片段

为啥我不能从 FragmentPagerAdapter 分离片段?

使用 ViewPager + FragmentPagerAdapter 处理方向更改

ViewPager中的FragmentPagerAdapter,FragmentStatePagerAdapter

使用 FragmentPagerAdapter 时如何获取现有片段