程序员人生 网站导航

【Android】从源码分析PagerAdapter/FragmentPagerAdapter调用notifydataSetChanged()刷新的原理

栏目:综合技术时间:2015-05-13 08:19:58

相信誉过viewpager的同学都会遇到调用notifydataSetChanged()后不刷新或不符合预期的问题,今天就来分析分析这里的来龙去脉。这1切还得从viewpager的setAdapter说起:

/** * Set a PagerAdapter that will supply views for this pager as needed. * * @param adapter Adapter to use */ public void setAdapter(PagerAdapter adapter) { ...(省略若干行,下同) ... final PagerAdapter oldAdapter = mAdapter; mAdapter = adapter; mExpectedAdapterCount = 0; if (mAdapter != null) { if (mObserver == null) { mObserver = new PagerObserver(); } mAdapter.registerDataSetObserver(mObserver); ... ... } ... }

mAdapter.registerDataSetObserver(mObserver)这里用到了视察者模式,mObserver是PagerObserver的1个实例,而PagerObserver是ViewPager的1个内部类,其声明以下:

private class PagerObserver extends DataSetObserver { @Override public void onChanged() { //这里调用了viewpager的dataSetChanged()方法,下同 dataSetChanged(); } @Override public void onInvalidated() { dataSetChanged(); } }

所以当mAdapter数据有变化调用notifydatasetchanged()刷新时就会调用到PagerObserver 的onChanged方法,继而调用到了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 (needPopulate) { ... ... setCurrentItemInternal(newCurrItem, false, true); requestLayout(); } }

这里看到,调用mAdapter.getItemPosition,如果返回的值是POSITION_UNCHANGED(PagerAdapter的默许实现),则needPopulate为false,就不会调用到setCurrentItemInternal(里面间接调用到instantiateItem(),后面讲到),所以就不会刷新视图。反之,如果返回值是POSITION_NONE,则needPopulate为true,就会调用到setCurrentItemInternal:

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { ... populate(item); ... }

而populate(item)里面会调用到addNewItem:

ItemInfo addNewItem(int position, int index) { ItemInfo ii = new ItemInfo(); ii.position = position; ii.object = mAdapter.instantiateItem(this, position); ii.widthFactor = mAdapter.getPageWidth(position); if (index < 0 || index >= mItems.size()) { mItems.add(ii); } else { mItems.add(index, ii); } return ii; }


这里看到调用到了mAdapter.instantiateItem(this, position);而PagerAdapter的instantiateItem里甚么都没做。

所以如果我们用的是PagerAdapter,我们需要复写instantiateitem,例如我们可以这么写:

@Override public Object instantiateItem(ViewGroup view, int position) { view.addView(mList.get(position)); return mList.get(position); }

而对FragmentPagerAdapter,它复写了instantiateitem:

@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; }

其中,getItemId的默许实现:

public long getItemId(int position) { return position; }
也就是item对应的下标就是item的id。另外makeFragmentName的实现:

private static String makeFragmentName(int viewId, long id) { return "android:switcher:" + viewId + ":" + id; }
这里makeFragmentName相当于为这个fragment组装成1个标识Tag,如果之前没添加过这个fragment,也就是mFragmentManager.findFragmentByTag(name)返回null,那末就调用 getItem(position);获得fragment,然后把这个fragment添加进去。

反之,也就是之前已添加过这个fragment了,则不会调用 getItem(position)了,而是直接attach上这个fragment。


参考

http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html









------分隔线----------------------------
------分隔线----------------------------

最新技术推荐