【Android】从源码分析PagerAdapter/FragmentPagerAdapter调用notifydataSetChanged()刷新的原理
相信用过viewpager的同学都会遇到调用notifydataSetChanged()后不刷新或者不符合预期的问题,今天就来分析分析这里的来龙去脉。这一切还得从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的一个实例,而PagerObserver是ViewPager的一个内部类,其声明如下:
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; }
所以如果我们用的是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组装成一个标识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
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。