ViewPager数据修改使用notifyDataSetChanged无刷新的问题
2016-04-20 19:48
405 查看
最近使用viewpager的时候遇到一个问题,viewpager设置过pagerAdapter之后,当需要修改viewpager的数据时,使用pagerAdapter.notifyDataSetChanged方法似乎并没有完全生效。例如,第一次设置viewpager的数据为2页,然后减少为1页时,会出现第2页仍然能翻动却不能停留在第2页的现象。接下来,通过读源码来找寻原因和解决方案。
那么首先,来看看API中对PagerAdapter的介绍。PagerAdapter用于将多个页面填充到ViewPager当中,实际中多数情况下,更倾向于使用一个更加具体的实现了PagerAdapter的适配器。实现一个PagerAdapter,必须至少重写以下方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
getCount()
isViewFromObject(View, Object)
PagerAdapter比很多AdapterView的适配器更加通用。ViewPager使用回调来显示一次更新的操作步骤,而不是直接采用视图回收机制。如果需要,PagerAdapter也可以实现视图回收方式,或者使用一种更巧妙的方法来管理页面视图,比如页面使用fragment能够管理自身事务。
ViewPager将每一个页面与一个key关联起来,而不是直接操作页面。这个key用于跟踪和唯一标识一个指定页面,并且独立于adapter。调用PagerAdapter的startUpdate(ViewGroup)方法,表示ViewPager的内容将要发生变化;接着会有一次或多次调用instantiateItem(ViewGroup, int)方法和/或destroyItem(ViewGroup, int, Object)方法;最后调用finishUpdate(ViewGroup),标志着这一次刷新完成。当finishUpdate(ViewGroup)执行完成,与instantiateItem(ViewGroup, int)返回的key相关联的视图,被加入到父ViewGroup当中;而与传递到destroyItem(ViewGroup, int, Object)的key相关联的视图,会被父ViewGroup移除。isViewFromObject(View, Object)方法用于判断一个视图是否与一个指定的key相关联。
一个简单的PagerAdapter可能选择页面视图本身作为key,在创建视图并加入父ViewGroup后通过instantiateItem(ViewGroup, int)返回。相对应的,destroyItem(ViewGroup, int, Object)的实现即将视图从父ViewGroup中移除,isViewFromObject(View, Object)可以实现为return view == object。
PagerAdapter支持数据集的改变。数据集的改变必须发生在主线程,并且必须以调用notifyDataSetChanged()方法结束,这与继承自BaseAdapter的AdapterView的适配器类似。一个数据集的改变,可能涉及页面的增、删及位置改变。Viewpager通过在适配器中实现getItemPosition(Object)方法来保持当前页面处于运行状态。
该方法调用抽象基类PagerAdapter的notifyDataSetChanged()方法。
mObservable是一个DataSetObservable对象,跟进方法,进入到DataSetObservable类中。DataSetObservable对象负责向list当中的所有DataSetObserver对象发送指示。notifyChanged()当中,当数据集变化,调用每个Observer的onChange()方法。
其中,Observable是观察者模式的应用,Observable类是一个泛型抽象类,表示一个观察者对象,提供了观察者注册、取消注册和清空三个方法。DataSetObservable直接继承Observable,使用DataSetObserver实例化了Observable。
DataSetObserver表示一个数据集对象的观察者,接收数据集变化或者失效两种回调。
在ViewPager当中发现PagerObserver,继承了DataSetObserver抽象类,实现了onChange()和onInvalidated()两个方法。
接下来,看ViewPager的dataSetChanged()方法。
重点看在于mAdapter.getItemPosition(Object)的返回结果。API中对该方法的说明是:该方法在主视图想要判断一个item的位置是否发生变化的时候调用。返回POSITION_UNCHANGED表示给定的item位置没有变化,返回POSITION_NONE表示给定的item已经不存在了。
而API中默认的实现是返回POSITION_UNCHANGED,即假设item的位置永远不会发生变化。这就能解释,为什么在删除或修改数据时,PagerAdapter不能只是通过notifyDataSetChanged实现刷新了。
当视图比较复杂时,这样写可能会增加开销,那么可以根据自己的需要来实现getItemPosition()方法。例如仅需要对某个特定的view更新,可以通过给该view设置tag,在需要更新时,通过tag定位到这个view进行更新。
一、ViewPager和PagerAdapter简介
API中对ViewPager的说明是:一种允许用户左右滑动页面的布局管理器,可以通过实现一个PagerAdapter来产生要展示的页面。那么首先,来看看API中对PagerAdapter的介绍。PagerAdapter用于将多个页面填充到ViewPager当中,实际中多数情况下,更倾向于使用一个更加具体的实现了PagerAdapter的适配器。实现一个PagerAdapter,必须至少重写以下方法:
instantiateItem(ViewGroup, int)
destroyItem(ViewGroup, int, Object)
getCount()
isViewFromObject(View, Object)
PagerAdapter比很多AdapterView的适配器更加通用。ViewPager使用回调来显示一次更新的操作步骤,而不是直接采用视图回收机制。如果需要,PagerAdapter也可以实现视图回收方式,或者使用一种更巧妙的方法来管理页面视图,比如页面使用fragment能够管理自身事务。
ViewPager将每一个页面与一个key关联起来,而不是直接操作页面。这个key用于跟踪和唯一标识一个指定页面,并且独立于adapter。调用PagerAdapter的startUpdate(ViewGroup)方法,表示ViewPager的内容将要发生变化;接着会有一次或多次调用instantiateItem(ViewGroup, int)方法和/或destroyItem(ViewGroup, int, Object)方法;最后调用finishUpdate(ViewGroup),标志着这一次刷新完成。当finishUpdate(ViewGroup)执行完成,与instantiateItem(ViewGroup, int)返回的key相关联的视图,被加入到父ViewGroup当中;而与传递到destroyItem(ViewGroup, int, Object)的key相关联的视图,会被父ViewGroup移除。isViewFromObject(View, Object)方法用于判断一个视图是否与一个指定的key相关联。
一个简单的PagerAdapter可能选择页面视图本身作为key,在创建视图并加入父ViewGroup后通过instantiateItem(ViewGroup, int)返回。相对应的,destroyItem(ViewGroup, int, Object)的实现即将视图从父ViewGroup中移除,isViewFromObject(View, Object)可以实现为return view == object。
PagerAdapter支持数据集的改变。数据集的改变必须发生在主线程,并且必须以调用notifyDataSetChanged()方法结束,这与继承自BaseAdapter的AdapterView的适配器类似。一个数据集的改变,可能涉及页面的增、删及位置改变。Viewpager通过在适配器中实现getItemPosition(Object)方法来保持当前页面处于运行状态。
二、ViewAdapter的notifyDataSetChanged()方法
那么,为什么数据集改变调用notifyDataSetChanged()方法刷新有时候不好用呢。首先从这句话开始。adapter.notifyDataSetChanged();
该方法调用抽象基类PagerAdapter的notifyDataSetChanged()方法。
public void notifyDataSetChanged() { this.mObservable.notifyChanged(); }
mObservable是一个DataSetObservable对象,跟进方法,进入到DataSetObservable类中。DataSetObservable对象负责向list当中的所有DataSetObserver对象发送指示。notifyChanged()当中,当数据集变化,调用每个Observer的onChange()方法。
public void notifyChanged() { synchronized(mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } }
其中,Observable是观察者模式的应用,Observable类是一个泛型抽象类,表示一个观察者对象,提供了观察者注册、取消注册和清空三个方法。DataSetObservable直接继承Observable,使用DataSetObserver实例化了Observable。
DataSetObserver表示一个数据集对象的观察者,接收数据集变化或者失效两种回调。
public abstract class DataSetObserver { public void onChanged() { // Do nothing } public void onInvalidated() { // Do nothing } }
在ViewPager当中发现PagerObserver,继承了DataSetObserver抽象类,实现了onChange()和onInvalidated()两个方法。
private class PagerObserver extends DataSetObserver { private PagerObserver() { } public void onChanged() { ViewPager.this.dataSetChanged(); } public void onInvalidated() { ViewPager.this.dataSetChanged(); } }
接下来,看ViewPager的dataSetChanged()方法。
void dataSetChanged() { // This method only gets called if our observer is attached, so mAdapter is non-null. final int adapterCount = mAdapter.getCount(); mExpectedAdapterCount = adapterCount; boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < adapterCount; int newCurrItem = mCurItem; boolean isUpdating = false; 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; } if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; if (!isUpdating) { mAdapter.startUpdate(this); isUpdating = true; } mAdapter.destroyItem(this, ii.position, ii.object); needPopulate = true; if (mCurItem == ii.position) { // Keep the current item in the valid range newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); needPopulate = true; } continue; } if (ii.position != newPos) { if (ii.position == mCurItem) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii.position = newPos; needPopulate = true; } } if (isUpdating) { mAdapter.finishUpdate(this); } Collections.sort(mItems, COMPARATOR); if (needPopulate) { // Reset our known pag 9fe3 e widths; populate will recompute them. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) { lp.widthFactor = 0.f; } } setCurrentItemInternal(newCurrItem, false, true); requestLayout(); } }
重点看在于mAdapter.getItemPosition(Object)的返回结果。API中对该方法的说明是:该方法在主视图想要判断一个item的位置是否发生变化的时候调用。返回POSITION_UNCHANGED表示给定的item位置没有变化,返回POSITION_NONE表示给定的item已经不存在了。
而API中默认的实现是返回POSITION_UNCHANGED,即假设item的位置永远不会发生变化。这就能解释,为什么在删除或修改数据时,PagerAdapter不能只是通过notifyDataSetChanged实现刷新了。
public int getItemPosition(Object object) { return POSITION_UNCHANGED; }
三、解决
比较简单的方法是:在Adapter中重写getItemPosition()方法,强迫viewpager重绘所有item。@Override public int getItemPosition(Object object) { return POSITION_NONE; }
当视图比较复杂时,这样写可能会增加开销,那么可以根据自己的需要来实现getItemPosition()方法。例如仅需要对某个特定的view更新,可以通过给该view设置tag,在需要更新时,通过tag定位到这个view进行更新。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories