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


这里看到调用到了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组装成一个标识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









郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。