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 分离片段?
使用 ViewPager + FragmentPagerAdapter 处理方向更改