ViewPager PagerAdapter notifyDataSetChanged无效问题

纵所周知,我们无论在使用RecyclerView还是ListView时,在更新完数据源以后都会调用adapter的notifyDataSetChanged()方法来更新视图。而PagerAdapter就是ViewPager使用的adapter,但是在实际使用中,发现更新数据源并不一定会更新视图。

我们来看看谷歌对PagerAdapter的这个方法notifyDataSetChanged()的定义

ViewPager PagerAdapter notifyDataSetChanged无效问题

 

 

 

 个人认为谷歌的这个方法的定义并没有很好的解释我工作中遇到的问题,所以就试试看看源码。。。

/**
 * This method should be called by the application if the data backing this adapter has changed
 * and associated views should update.
 */
public void notifyDataSetChanged() {
    synchronized (this) {
        if (mViewPagerObserver != null) {
            mViewPagerObserver.onChanged();
        }
    }
    mObservable.notifyChanged();
}

一看就知道是观察者模式,这里我们只关注mViewPagerObserver,看看它是哪里被赋值的。

void setViewPagerObserver(DataSetObserver observer) {
    synchronized (this) {
        mViewPagerObserver = observer;
    }
}

再看看这个方法在哪里被调用的。

/**
 * Set a PagerAdapter that will supply views for this pager as needed.
 *
 * @param adapter Adapter to use
 */
public void setAdapter(PagerAdapter adapter) {
    if (mAdapter != null) {
        mAdapter.setViewPagerObserver(null);
        mAdapter.startUpdate(this);
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            mAdapter.destroyItem(this, ii.position, ii.object);
        }
        mAdapter.finishUpdate(this);
        mItems.clear();
        removeNonDecorViews();
        mCurItem = 0;
        scrollTo(0, 0);
    }

    final PagerAdapter oldAdapter = mAdapter;
    mAdapter = adapter;
    mExpectedAdapterCount = 0;

    if (mAdapter != null) {
        if (mObserver == null) {
            mObserver = new PagerObserver();
        }
        mAdapter.setViewPagerObserver(mObserver);
        mPopulatePending = false;
        final boolean wasFirstLayout = mFirstLayout;
        mFirstLayout = true;
        mExpectedAdapterCount = mAdapter.getCount();
        if (mRestoredCurItem >= 0) {
            mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
            setCurrentItemInternal(mRestoredCurItem, false, true);
            mRestoredCurItem = -1;
            mRestoredAdapterState = null;
            mRestoredClassLoader = null;
        } else if (!wasFirstLayout) {
            populate();
        } else {
            requestLayout();
        }
    }

    // Dispatch the change to any listeners
    if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
        for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
            mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
        }
    }
}

在上述代码里看到是在setAdapter这个方法里进行赋值,并且看到mObserver = new PagerObserver();mObserver这个对象也是这个方法里创建的,再看看PagerObserver这个类,是个私有的内部类。

private class PagerObserver extends DataSetObserver {
    PagerObserver() {
    }

    @Override
    public void onChanged() {
        dataSetChanged();
    }
    @Override
    public void onInvalidated() {
        dataSetChanged();
    }
}

现在清楚了,notifyDataSetChanged()方法里mViewPagerObserver.onChanged()执行的是dataSetChanged()方法,我们再看看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 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();
    }
}

看到红色标注处,再看看getItemPosition()这个方法,

/**
 * Called when the host view is attempting to determine if an item's position
 * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
 * item has not changed or {@link #POSITION_NONE} if the item is no longer present
 * in the adapter.
 *
 * <p>The default implementation assumes that items will never
 * change position and always returns {@link #POSITION_UNCHANGED}.
 *
 * @param object Object representing an item, previously returned by a call to
 *               {@link #instantiateItem(View, int)}.
 * @return object's new position index from [0, {@link #getCount()}),
 *         {@link #POSITION_UNCHANGED} if the object's position has not changed,
 *         or {@link #POSITION_NONE} if the item is no longer present.
 */
public int getItemPosition(Object object) {
    return POSITION_UNCHANGED;
}

默认返回POSITION_UNCHANGED,这就导致了数据源更新了,但是视图没有变化。

所以本人的解决方法就是重写getItemPosition()这个方法,使其返回POSITION_NONE。当然这种方法对系统的开销很大,每次都要执行destroyItem()方法,如果数据量不大,就会造成很多不必要的开销。