ViewPager是Android应用开发中非常常用的一个控件,是一个可以让View左右翻页滑动的管理布局,需要和PagerAdapter配合使用,来创建每一页的View并显示。
ViewPager的使用其实是比较简单的,但是有一个比较重要的问题,就是ViewPager的数据刷新。PagerAdapter有一个notifyDataSetChanged()方法,根据Android官方文档的介绍
PagerAdapter支持数据的更改,但是数据的更改必须在主线程中进行,并且要调用notifyDataSetChanged()这个方法。我们知道所谓数据的更改,无非是页面的添加,删除和页面位置的变化。
按照官方文档的介绍,我们在改变PagerAdapter的数据源之后,然后调用一下notifyDataSetChanged()方法,应该就可以看到页面的变化,然而事情并没有那么简单,你会发现页面并没有任何的变化。那这个到底是什么情况。我们来看一下PagerAdapter的notifyDataSetChanged()这个方法都干了些什么
public void notifyDataSetChanged() {
synchronized(this) {
if (this.mViewPagerObserver != null) {
this.mViewPagerObserver.onChanged();
}
}
this.mObservable.notifyChanged();
}
我们可以看到这里调用了this,mViewPagerObserver.onChanged(),这里实际上是观察者模式,去通知ViewPager数据发生了改变。我们再看下ViewPager的ViewPagerObserver 的onChanged ()方法又做了什么,
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
public void onChanged() {
ViewPager.this.dataSetChanged();
}
public void onInvalidated() {
ViewPager.this.dataSetChanged();
}
}
ViewPagerObserver的onChanged()方法里调用了ViewPager的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);
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;
}
}
}
if (isUpdating) {
this.mAdapter.finishUpdate(this);
}
Collections.sort(this.mItems, COMPARATOR);
if (needPopulate) {
childCount = this.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = this.getChildAt(i);
ViewPager.LayoutParams lp = (ViewPager.LayoutParams)child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.0F;
}
}
this.setCurrentItemInternal(newCurrItem, false, true);
this.requestLayout();
}
}
我们可以看到dataSetChanged()方法,中
int newPos = this.mAdapter.getItemPosition(ii.object);
if (newPos != -1) {
.......
}
newPos不等于-1才会进入刷新数据的逻辑,newPos来自于PagerAapter的getItemPosition()方法,我们再看一下PagerAdapter的getItemPosition()方法。
public int getItemPosition(@NonNull Object object) {
return -1;
}
你会惊讶的发现,这特码的直接返回-1,调用PagerAdapter的notifyDataSetChanged()
ViewPager肯定不会有任何刷新啊。也是醉了。这算是Google工程师给的坑?。回到正题,那怎么办呢,我们应该需要重写PagerAdapter的getItemPosition()方法。PagerAdapter中实际上定义了两个常量。
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
默认的返回的是POSITION_UNCHANGED,我们重写getItemPosition()方法返回POSITION_NONE就可以实现数据的刷新了。
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE ;
}
但是这样做有个严重缺点就是,所有的getItemPosition()都返回POSITION_NONE的话,
每次都要移除所有的View,再重新添加到ViewPager中,这样效率太低。
有一个比较好的优化方案是:首先在instantiateItem()方法中给每个View设置一个Tag,就是View在数据列表也就是ViewPager中的索引位置,然后在getItemPosition()判断,当前View数据列表中是否有该View,索引位置有没有发生该变,如果当前数据列表没有该View int newsPos=mViewList.indexOf(object),也即newPos为-1,返回POSITION_NONE即可;如果当前数据列表中有该View,但是索引位置发生了改变也即 int oldPosition= (int) ((View) object).getTag();
newPosition!=oldPosition ,直接返回新的索引newPosition,如果位置没有发生改变
则返回POSITION_UNCHANGED。
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
mViewList.get(position).setTag(position); //设置Tag
container.addView(mViewList.get(position));
return mViewList.get(position);
}
@Override
public int getItemPosition(@NonNull Object object) {
int oldPosition= (int) ((View) object).getTag();
int newsPos=mViewList.indexOf(object);
if (newsPos>0&&newsPos!=oldPosition){
((View) object).setTag(newsPos);
return newsPos;
}else{
if (newsPos<0){
return POSITION_NONE;
}else{
return POSITION_UNCHANGED;
}
}
}
以上就是ViewPager中数据刷新的问题的分析及解决。