推荐
推荐使用 ViewPager2
可以更好的解决复用问题
前言
ViewPager是一个经常使用的组件,但是当页面越来越多时,由于各种原因(如内存泄露等等),会导致崩溃(内存溢出),好了,进入正题
本文出现的vp为ViewPager简写
vp页面的添加&删除
由于vp有预加载功能,会有两种加载情况(可能不止?)
1.先加载当前页,再从左到右加载or删除页面(左滑右滑)
2.跳转到某页的情况(如第1页跳转第10页),会优先加载跳转页&预加载页,再删除之前的旧页面(如加载10,9,11再删除1,2)
即真正需要用到的页面数其实是:预加载页 + 1个当前页+1缓存页
//把一侧预加载设置为2,即需要用到的页面为 2 * 2 + 1 + 1 = 6 个页面,vp预加载默认为1即缓存4个页面足以
vp.setOffscreenPageLimit(2);
那么我们可以用一个定长数组把这些页面缓存起来,然后通过复用这些页面达到资源合理利用,先贴代码
该适配器,弱引用防止Context泄漏,T为页面数据模型,H为页面Holder,View数组为缓存页面的数组
mUnRemoveTags这个int集合是个重点,用于标记vp调用destroyItem时是否删除页面,跟vp从左到右添加or删除有关
提供两个构造方法,一个为默认预加载为1,另一个可自行设置也加载数
public abstract class RecyclePagerAdapter<T, H extends RecyclePagerAdapter.PagerHolder> extends PagerAdapter {
private final WeakReference<Context> mContext;
private final List<T> mInfos;
private final View[] mCacheView;
//不删除记号,用于destroyItem时不清除view
private final Set<Integer> mUnRemoveTags = new HashSet<>();
public RecyclePagerAdapter(Context context, List<T> infos) {
this(context, infos, 1);
}
/**
* @param pagerLimit 预加载一侧页数(即预加载总数为pagerLimit * 2)
*/
public RecyclePagerAdapter(Context context, List<T> infos, int pagerLimit) {
if(infos == null)
throw new NullPointerException();
if(pagerLimit < 1)
pagerLimit = 1;
this.mContext = new WeakReference<>(context);
this.mInfos = infos;
//(pagerLimit * 2)(预加载数) + 1(当前页) + 1(缓存页)
mCacheView = new View[(pagerLimit + 1) * 2];
}
......
}
再添加两个获取页面数据&Context的方法
{
...
public List<T> getPagerInfos(){
return mInfos;
}
public Context getContext(){
return mContext.get();
}
...
}
现在再来看下PagerHolder,这个跟ListView的Holder差不多,就不多解释
public static class PagerHolder{
public final View view;
public PagerHolder(View view) {
view.setTag(this);
this.view = view;
}
}
国际惯例getCount,isViewFromObject,都是原本的写法~
基本需要数据&方法都差不多了,现在来看看重点,添加&删除页面,这里我们先添加三个抽象方法供子类实现,与recycleview适配器类似,用于创建view,holder和绑定view数据,释放view
{
....
/**
* 创建一个Holder
* @param position 页面下标
* @return holder
*/
protected abstract H onCreaterPagerHolder(Context context, int position);
/**
* 绑定页面数据
* @param info 页面数据
* @param position 页面下标
*/
protected abstract void onBindPagerHolde(H holder, T info, int position);
/**
* 用于释放页面destroyItem调用且移除时调用
*/
protected void onReleasePagerHolde(H holder, int position){}
....
}
接下来就到添加&删除页面了
1.添加view,通过position对缓存长度取模确定缓存下标,再判断是否需要新建页面,若页面已存在vp中,则记录下标,到destroyItem时不删除该页面,(复用)
2.删除view,通过已记录缓存下标判(mUnRemoveTags)断是否删除页面
{
......
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
//获取缓存下标
int index = position % mCacheView.length;
T info = mInfos.get(position);
H holder;
if(mCacheView[index] == null){
holder = onCreaterPagerHolder(mContext.get(), position);
mCacheView[index] = holder.view;
}else{
holder = (H) mCacheView[index].getTag();
}
//获取容器是否存在该View
int i = container.indexOfChild(holder.view);
if(i != -1)
//存在则记录缓存下标
mUnRemoveTags.add(index);
else
container.addView(holder.view);
onBindPagerHolde(holder, info, position);
return holder.view;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
int index = position % mCacheView.length;
//检查是否在不删除标记内,是则不移除view,否则移除view
if(!mUnRemoveTags.contains(index)) {
View view = mCacheView[index];
onReleasePagerHolde((H) view.getTag(), position);
container.removeView(view);
}else {
mUnRemoveTags.remove(index);
}
}
......
}
好了,到这里,vp复用就完了,目前,缺陷是只能用于同一种布局,用于多布局的话可以配合recycleview Or ListView等等使用
完整适配器代码
/**
* Created by xiaobaicz
*/
public abstract class RecyclePagerAdapter<T, H extends RecyclePagerAdapter.PagerHolder> extends PagerAdapter {
private final WeakReference<Context> mContext;
private final List<T> mInfos;
private final View[] mCacheView;
//不删除记号,用于destroyItem时不清除view
private final Set<Integer> mUnRemoveTags = new HashSet<>();
public RecyclePagerAdapter(Context context, List<T> infos) {
this(context, infos, 1);
}
/**
* @param pagerLimit 预加载一侧页数(即预加载总数为pagerLimit * 2)
*/
public RecyclePagerAdapter(Context context, List<T> infos, int pagerLimit) {
if(infos == null)
throw new NullPointerException();
if(pagerLimit < 1)
pagerLimit = 1;
this.mContext = new WeakReference<>(context);
this.mInfos = infos;
//(pagerLimit * 2)(预加载数) + 1(当前页) + 1(缓存页)
mCacheView = new View[(pagerLimit + 1) * 2];
}
@Override
public int getCount() {
return mInfos.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
//获取缓存下标
int index = position % mCacheView.length;
T info = mInfos.get(position);
H holder;
if(mCacheView[index] == null){
holder = onCreaterPagerHolder(mContext.get(), position);
mCacheView[index] = holder.view;
}else{
holder = (H) mCacheView[index].getTag();
}
//获取容器是否存在该View
int i = container.indexOfChild(holder.view);
if(i != -1)
//存在则记录缓存下标
mUnRemoveTags.add(index);
else
container.addView(holder.view);
onBindPagerHolde(holder, info, position);
return holder.view;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
int index = position % mCacheView.length;
//检查是否在不删除标记内,是则不移除view,否则移除view
if(!mUnRemoveTags.contains(index)) {
View view = mCacheView[index];
onReleasePagerHolde((H) view.getTag(), position);
container.removeView(view);
}else {
mUnRemoveTags.remove(index);
}
}
/**
* 创建一个Holder
* @param position 页面下标
* @return holder
*/
protected abstract H onCreaterPagerHolder(Context context, int position);
/**
* 绑定页面数据
* @param info 页面数据
* @param position 页面下标
*/
protected abstract void onBindPagerHolde(H holder, T info, int position);
/**
* 用于释放页面destroyItem调用且移除时调用
*/
protected void onReleasePagerHolde(H holder, int position){}
public List<T> getPagerInfos(){
return mInfos;
}
public Context getContext(){
return mContext.get();
}
public static class PagerHolder{
public final View view;
public PagerHolder(View view) {
view.setTag(this);
this.view = view;
}
}
}
简单用法
/**
* Created by xiaobaicz
*/
public class MyAdapter extends RecyclePagerAdapter<Integer, MyAdapter.Holder> {
public MyAdapter(Context context, List<Integer> infos) {
super(context, infos);
}
public MyAdapter(Context context, List<Integer> infos, int pagerLimit) {
super(context, infos, pagerLimit);
}
@Override
protected Holder onCreaterPagerHolder(Context context, int position) {
return new Holder(View.inflate(context, R.layout.pager, null));
}
@Override
protected void onBindPagerHolde(Holder holder, Integer info, int position) {
holder.view.setBackgroundColor(info);
}
static class Holder extends RecyclePagerAdapter.PagerHolder {
Holder(View view) {
super(view);
}
}
}