最近在工作中遇到了一个问题,就是在viewpager中调用pageradapter.notifydatasetchanged方法,好像没有任何效果,相应的view也没有更新数据,根据官方API是这样解释的:大概是说明Adapter会自动管辖ViewPager每一页(Item)的状态,而notifyDataSetChanged()是用在当ViePager要新增一页、刪除一页或改变各个页面的排列的時候。所以ViewPager Adapter的notifyDataSetChanged自然就不适用于只更新View Pager里面某个View的內容的需求。
通常的解决方案是重写 PagerAdapter 的 getItemPosition() 方法,并返回 POSITION_NONE ,以触发 PagerAdapter 销毁并重建对象。
下面我们通过源代码分析为什么必须 override这个方法。
首先看 PagerAdapter 源代码,它的 notifiDataSetChanged() 方法内部调用了一个 DataSetObservable 对象的 notifyChanged() 方法:
/** * This method should be called by the application if the data backing this adapter has changed * and associated views should update. */ public void notifyDataSetChanged() { mObservable.notifyChanged(); }
跟进这个方法,我们找到DataSetObservable的notifyChanged方法:
/** * Invokes {@link DataSetObserver#onChanged} on each observer. * Called when the contents of the data set have changed. The recipient * will obtain the new contents the next time it queries the data set. */ public void notifyChanged() { synchronized(mObservers) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } }
mObservers的类型是一个抽象类DataSetObserver,里面只有两个为实现的方法:
/** * Receives call backs when a data set has been changed, or made invalid. The typically data sets * that are observed are {@link Cursor}s or {@link android.widget.Adapter}s. * DataSetObserver must be implemented by objects which are added to a DataSetObservable. */ public abstract class DataSetObserver { /** * This method is called when the entire data set has changed, * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}. */ public void onChanged() { // Do nothing } /** * This method is called when the entire data becomes invalid, * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a * {@link Cursor}. */ public void onInvalidated() { // Do nothing } }
都有谁使用了这个抽象类呢,ctrl+T可以查看调用者
进入viewpager,找到控制数据更新的重点代码:
void dataSetChanged() { // This method only gets called if our observer is attached, so mAdapter is non-null. boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < mAdapter.getCount(); 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);
//根据getItemPosition的返回值,来决定是否调用destroyItem方法。
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, mAdapter.getCount() - 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 page 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(); } }
从上面我们看到,它会判断adapter的getItemPosition方法的返回值,只有当返回值是POSITION_NONE时候,才会调用item的remove方法以及startUpdate和destroyItem方法,进而去更新数据。默认返回值是POSITION_UNCHANGED,不执行任何操作。
PagerAdapter 的 getItemPosition() 方法的默认实现:
public int getItemPosition(Object object) { return POSITION_UNCHANGED; }
看到这里,大家基本就可以理解为什么我们要覆盖这个方法了。这个方法很简单,但是每次调用adapter都会去调用destroy方法,因此最好是自己做view的缓存,destroy方法可以默认不处理,下面贴上简单实现:
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override public Object instantiateItem(View container, int position) {
//下面是我自己定义的View对象,也可以用layoutinflat xml文件的方法
CustomerView view = viewList[position]; ViewGroup viewpager = ((ViewGroup) container); if(view == null) { view = new CustomerView (mContext); viewList[position] = view; (viewpager).addView(view, 0); } return view; } @Override public void destroyItem(View container, int position, Object object) {
container.removeView(viewList[position]);
}
参考:http://rincliu.com/blog/2013/09/29/viewpager/