前言:有时候需要动态改变tab条目数,需要动态去改变Fragment,根据API不管是使用FragmentPagerAdapter或者
FragmentStatePagerAdapter都无法直接实现。
原因分析:
根据习惯我们都会调用 mAdapter.notifyDataSetChanged(),但是viewpager+FragmentPagerAdapter(或FragmentStatePagerAdapter)并无反应,因此我们来看看源代码在notifyDataSetChanged()后执行了啥,
private DataSetObserver mViewPagerObserver;
.
.
.
public void notifyDataSetChanged() {
synchronized(this) {
if (this.mViewPagerObserver != null) {
//调用了onChanged
this.mViewPagerObserver.onChanged();
}
}
this.mObservable.notifyChanged();
}
DataSetObserver 为一个抽象类,在ViewPager中实现
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
public void onChanged() {
//此处调用dataSetChanged
ViewPager.this.dataSetChanged();
}
public void onInvalidated() {
ViewPager.this.dataSetChanged();
}
}
关键方法dataSetChanged(),来看
void dataSetChanged() {
int adapterCount = this.mAdapter.getCount();
this.mExpectedAdapterCount = adapterCount;
boolean needPopulate = this.mItems.size() < this.mOffscreenPageLimit * 2 + 1 && this.mItems.size() < adapterCount;
int newCurrItem = this.mCurItem;
boolean isUpdating = false;
int childCount;
for(childCount = 0; childCount < this.mItems.size(); ++childCount) {
ViewPager.ItemInfo ii = (ViewPager.ItemInfo)this.mItems.get(childCount);
//关键处getItemPosition()的值 只有等于-2的时候才会执行里面方法
int newPos = this.mAdapter.getItemPosition(ii.object);
if (newPos != -1) {
if (newPos == -2) {
this.mItems.remove(childCount);
--childCount;
if (!isUpdating) {
this.mAdapter.startUpdate(this);
isUpdating = true;
}
this.mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (this.mCurItem == ii.position) {
newCurrItem = Math.max(0, Math.min(this.mCurItem, adapterCount - 1));
needPopulate = true;
}
} else if (ii.position != newPos) {
if (ii.position == this.mCurItem) {
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
}
.....省去部分源码
}
}
再看getItemPosition()
//public abstract class PagerAdapter
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
.......
//默认返回-1
public int getItemPosition(@NonNull Object object) {
return -1;
}
因此我们只需重写FragmentPagerAdapter和 FragmentStatePagerAdapter的父类PagerAdapter里面的getItemPosition方法:
@Override
public int getItemPosition(@NonNull Object object) {
return PagerAdapter.POSITION_NONE;
}
到这里当我没调用 mAdapter.notifyDataSetChanged();时数据就会更新了,但是并不能做到动态删除、添加。因此再看FragmentPagerAdapter和FragmentStatePagerAdapter 对Fragment对象的操作方法,
也就是instantiateItem()创建和destroyItem()移除fragment的方法。
我们先来看FragmentPagerAdapter的instantiateItem()创建和destroyItem()移除fragment的方法,源码已做好注释
@Override
@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
//这里获取的itemId,就是position
long itemId = this.getItemId(position);
// fragment都是以container的id+itemId(gettemId()) 两个参数计算出来作为fragment的储存的tag key值,
String name = makeFragmentName(container.getId(), itemId);
//其在instantiateItem方法中并不是直接去List里面拿到Fragment的,而是先从FragmentManager中通过Tag找对应的Fragment
Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
this.mCurTransaction.attach(fragment);
} else {
fragment = this.getItem(position);
this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
}
if (fragment != this.mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
解决方法一
private List<Fragment> mFragments = new ArrayList<>();
class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
return mFragments.get(i);
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mTitles.get(position);
}
@Override
public int getItemPosition(@NonNull Object object) {
return PagerAdapter.POSITION_NONE;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.destroyItem(container, position, object);
}
@Override
public long getItemId(int position) {
//修改getItemId 不与位置对应 返回List<Fragment>中Fragment的hashCode
return mFragments.get(position).hashCode();
}
}
解决方法二,原理是将FragmentManager换成的fragmengt对应的key保存下来,在删除新增和改变tab时,将原来的集合清空和FragmentManager中的缓存移除,相当于重新setAdapter
private class ChangePagerAdapter extends FragmentPagerAdapter {
private FragmentManager fm;
private List<Fragment> mFragments;
private List<String> myTag = new ArrayList<>();
public ChangePagerAdapter (FragmentManager fm, List<Fragment> mFragments) {
super(fm);
this.mFragments = mFragments;
this.fm = fm;
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
//将生成的key保存起来
myTag .add(makeFragmentName(container.getId(), getItemId(position)));
Fragment fragment = (Fragment) super.instantiateItem(container, position);
this.fm.beginTransaction().show(fragment).commitAllowingStateLoss();
return fragment;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
private String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
//改变的时候调用 将所有的fragment缓存全部删除
public void reMoveAll() {
if (this.myTag != null) {
FragmentTransaction fragmentTransaction = fm.beginTransaction();
for (int i = 0; i < tags.size(); i++) {
Fragment fragmentO = fm.findFragmentByTag(myTag.get(i));
fragmentTransaction.remove(fragmentO);
}
fragmentTransaction.commitAllowingStateLoss();
fm.executePendingTransactions();
tags.clear();
}
notifyDataSetChanged();
}
//清空集合 然后在重新set数据 相当于重新setAdapter了
public void clearAll() {
mFragments.clear();
notifyDataSetChanged();
}
}
三、我们再看FragmentStatePagerAdapter,只需要重写getItemPosition并返回PagerAdapter.POSITION_NONE
class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
return mFragments.get(i);
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mTitles.get(position);
}
@Override
public int getItemPosition(@NonNull Object object) {
return PagerAdapter.POSITION_NONE;
}
}
解决办法四、参考FragmentPagerAdapter和FragmentStatePagerAdapter的实现方法,自己重写PagerAdapter父类的instantiateItem和destroyItem方法,自己维护缓存。
.......................
到此ViewPager就可以动态删除和调整fragment了。