ViewPager动态添加删除及刷新页面
文章目录
1. 前言
2. PagerAdapter 的刷新
2.1 源码解析
2.2 例子
3. FragmentPagerAdapter的刷新
3.1 源码解析
3.2 参考代码
1. 前言
在此之前,我总是不得其解,同样都提供了**notifyDataSetChanged()**方法,为什么 ListView 的adapter使用刷新的方法非常好用,而 ViewPager 的adapter使用刷新方法总是有这样那样的问题?百度了一下,查阅了很多篇文章,难以找到满意的解决方案。尤其涉及到ViewPager动态添加删除及刷新页面时,刷新成了难以克服的痛点。有的说ViewPager不能刷新,有的说需要重写这个方法,有的说需要重写那个方法。
为了搞清楚ViewPager的刷新问题,我参考多篇博客,又研读了源码,费了不少脑力。最终,这个问题还是被我搞定了。
2. PagerAdapter 的刷新
2.1 源码解析
要想真正的理解PagerAdapter的刷新,就一定要从源码找突破口。下面是PagerAdapter的类注释。
/**
* ......
*
* <p>PagerAdapter supports data set changes. Data set changes must occur on the
* main thread and must end with a call to {@link #notifyDataSetChanged()} similar
* to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
* set change may involve pages being added, removed, or changing position. The
* ViewPager will keep the current page active provided the adapter implements
* the method {@link #getItemPosition(Object)}.</p>
*/
public abstract class PagerAdapter {
//......
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
//......
}
看PagerAdapter的最后一段,大概翻译一下,意思是:
PagerAdapter支持数据集改变。数据集的改变必须发生在主线程,并且以调用notifyDataSetChanged()方法结束,类似于AdapterView的适配器(继承自android.widget.BaseAdapter的)。数据集改变包括页面被添加、删除或位置改变。如果适配器实现了方法getItemPosition(Object),ViewPager将保持当前页面处于活动状态。
这段话有一个非常明确的信息:PagerAdapter可以通过调用notifyDataSetChanged()方法实现数据集的刷新。这就打破了有些人认为的ViewPager无法通过notifyDataSetChanged()刷新的认知。而且,这段话还告诉我们,我们可能需要实现方法getItemPosition(Object)。下面我面找到该方法的源码看一下:
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*
* <p>The default implementation assumes that items will never
* change position and always returns {@link #POSITION_UNCHANGED}.
*
* @param object Object representing an item, previously returned by a call to
* {@link #instantiateItem(View, int)}.
* @return object's new position index from [0, {@link #getCount()}),
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
需要看一下方法注释,大概意思是:
当主控件(个人认为是ViewPager)尝试确定item的位置是否已经改变时调用。如果给定的item位置没有改变,返回POSITION_UNCHANGED;如果item已经在适配器中不存在了,返回POSITION_NONE。
默认实现假设所有item永远不会改变位置并且总是返回POSITION_UNCHANGED(未改变)。
参数:object 代表一个item的对象(个人认为:View或Fragment),最初由instantiateItem(View, int) 方法返回。
返回 如果位置发生改变,返回对象的新位置,是0到getCount()的索引(不包括getCount());如果位置没有改变,返回 POSITION_UNCHANGED ;如果item不存在了,返回 POSITION_NONE 。
2.2 例子
@Override
public int getItemPosition(@NonNull Object object) {
if (mDataList.contains(object)) {
return mDataList.indexOf(object);
} else {
return POSITION_NONE;
}
}
注意:getItemPosition 的参数 object 与 adapter 的初始化方法:public Object instantiateItem(@NonNull ViewGroup container, int position) 的返回值有关,两者是对应关系。
其实这几个方法 isViewFromObject(view, object)、getItemPosition(object)、getItemPosition(object) 的 object 都与
instantiateItem(container, position) 的返回值一致。
可参考之后的文章
3. FragmentPagerAdapter的刷新
3.1 源码解析
由于FragmentPagerAdapter继承了PagerAdapter,那么是不是FragmentPagerAdapter只要重写了方法getItemPosition()就可以实现自己的刷新了?事实并非如此,即使我们重写了方法getItemPosition(),用FragmentPagerAdapter刷新界面时依然会有问题。查找博客,翻看源码,发现我们还需要重写一个FragmentPagerAdapter的方法getItemId()。下面是FragmentPagerAdapter的getItemId()方法的源码:
/**
* Return a unique identifier for the item at the given position.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
从方法描述我们可以看出:这个方法为给定position的item,返回一个唯一的id(identifier)。默认情况下,返回这个item的position。如果item的position可能发生改变,子类应该重写这个方法。
说的很清楚,如果ViewPager的item的position可能发生改变,子类应该重写这个方法。item的position发生改变的情况,恐怕只有添加,修改(互换位置),删除了。总之只要position可能发生改变,我们就需要重写这个方法。
所以,使用FragmentPagerAdapter除了需要重写其父类PagerAdapter的getItemPosition()方法,还需要重写getItemId()方法。
为什么FragmentPagerAdapter需要重写getItemId()方法呢?看一下FragmentPagerAdapter初始化item的方法:
@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
此方法中使用到了getItemId()方法,用于方法makeFragmentName()生成Fragment的tag。FragmentMaganger会通过这个tag查找和添加Fragment。
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
我们都知道,ViewPager有预加载前一页和后一页的功能,预加载的页面(Fragment)会添加到FragmentPagerAdapter的FragmentManager中,其中的Fragment应当与它的tag一一对应,ViewPager切换到position页,就会把对应的Fragment attach到mCurTransaction中。在destroyItem()方法中会移除超出界限的Fragment。
当所有item的position不发生改变时,通过getItemId()可以获得对应的Fragment
当我们的position发生改变时,同一个position,上面代码中查找的Fragment将不再是原来的Fragment (tag改变了),刷新界面就会出现错乱或报错。
所以,只要我们需要保证,也就是一个position对应一个id,通过position能获得原来的Fragment。
3.2 参考代码:
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
import com.zhxg.androidtv.fragment.yqfbindex.CommonPageFragment;
import java.util.ArrayList;
import java.util.List;
/**
* 用于动态改变页面(新增页面和删除页面)的FragmentPagerAdapter
* Created by wangzhengyang on 2017-12-27 0027.
*/
public class CommonPageAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragmentList = new ArrayList<>();
private List<Integer> mItemIdList = new ArrayList<>();
private int id = 0;
private FragmentManager mFm;
public CommonPageAdapter(FragmentManager fm, @NonNull List<Fragment> fragmentList) {
super(fm);
this.mFm = fm;
for (Fragment fragment : fragmentList) {
this.mFragmentList.add(fragment);
mItemIdList.add(id++);
}
}
public CommonPageAdapter(FragmentManager fm) {
super(fm);
}
public List<Fragment> getFragmentList() {
return mFragmentList;
}
public void addPage(int index, Fragment fragment){
mFragmentList.add(index, fragment);
mItemIdList.add(index, id++);
notifyDataSetChanged();
}
public void addPage(Fragment fragment){
mFragmentList.add(fragment);
mItemIdList.add(id++);
notifyDataSetChanged();
}
public void delPage(int index) {
mFragmentList.remove(index);
mItemIdList.remove(index);
notifyDataSetChanged();
}
public void delPage() {
mFragmentList.remove();
mItemIdList.remove();
notifyDataSetChanged();
}
public void updatePage(List<Fragment> fragmentList) {
mFragmentList.clear();
mItemIdList.clear();
for(int i = 0; i < fragmentList.size(); i++){
mFragmentList.add(fragmentList.get(i));
mItemIdList.add(id++);//注意这里是id++,不是i++。
}
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
/**
* 返回值有三种,
* POSITION_UNCHANGED 默认值,位置没有改变
* POSITION_NONE item已经不存在
* position item新的位置
* 当position发生改变时这个方法应该返回改变后的位置,以便页面刷新。
*/
@Override
public int getItemPosition(Object object) {
if (object instanceof Fragment) {
if (mFragmentList.contains(object)) {
return mFragmentList.indexOf(object);
} else {
return POSITION_NONE;
}
}
return super.getItemPosition(object);
}
@Override
public long getItemId(int position) {
return mItemIdList.get(position);
}
}
还可以利用Fragment对象的hashCode,不用维护一个idList,前提是没有重写Fragment的hashCode。代码如下:
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* 用于动态改变页面(新增页面和删除页面)的FragmentPagerAdapter
* Created by wangzhengyang.
*/
public class CommonPageAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragmentList = new ArrayList<>();
private FragmentManager mFm;
public CommonPageAdapter(FragmentManager fm, int behavior, @NonNull List<Fragment> fragmentList) {
super(fm, behavior);
this.mFm = fm;
for (Fragment fragment : fragmentList) {
this.mFragmentList.add(fragment);
}
}
public CommonPageAdapter(FragmentManager fm) {
super(fm);
}
public List<Fragment> getFragmentList() {
return mFragmentList;
}
public void addPage(int index, Fragment fragment){
mFragmentList.add(index, fragment);
notifyDataSetChanged();
}
public void addPage(Fragment fragment){
mFragmentList.add(fragment);
notifyDataSetChanged();
}
public void delPage(int index) {
mFragmentList.remove(index);
notifyDataSetChanged();
}
public void delPage(Fragment fragment) {
mFragmentList.remove(fragment);
notifyDataSetChanged();
}
public void updatePage(List<Fragment> fragmentList) {
mFragmentList.clear();
for(int i = 0; i < fragmentList.size(); i++){
mFragmentList.add(fragmentList.get(i));
}
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
/**
* 返回值有三种,
* POSITION_UNCHANGED 默认值,位置没有改变
* POSITION_NONE item已经不存在
* position item新的位置
* 当position发生改变时这个方法应该返回改变后的位置,以便页面刷新。
*/
@Override
public int getItemPosition(Object object) {
if (object instanceof Fragment) {
int index = mFragmentList.indexOf(object);
if (index != -1) {
return index;
} else {
return POSITION_NONE;
}
}
return super.getItemPosition(object);
}
@Override
public long getItemId(int position) {
return mFragmentList.get(position).hashCode();
}
}
那么请问要怎么在fragment中实现增删改呢