ViewPager calls notifyDataSetChanged() to refresh the solution

1, The cause of the problem

Viewpager control to a large extent meets the developer's development page left and right mobile switching function, very convenient to use. However, in use, it is found that when data is deleted or modified, PagerAdapter can't just notify to refresh View through notifyDataSetChanged method like BaseAdapter. A solution has been proposed: reset the adapter to the viewpager to refresh the data. But in most cases, this method is problematic.

2, Problem analysis

Why is the method of data update called, but the Viewpager is not updated? Let's follow up the source code of the method to have a look.

/**
 * 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();
}

Note that this method should be called to refresh the data when the data attached to the adapter changes. This method calls a myobservable. 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();
         }
    }
}

 

This is not the point. Let's focus on the type of this mbobserver, which is an abstract class DataSetObserver. There are only two unimplemented methods in it. Who has used this abstract class? The shortcut key ctrl + alt + H. among many callers, we have found the figure of Viewpager. After entering the Viewpager, we finally found the key method of controlling data change in the Viewpager, dataSetChanged, which is as follows:

      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 );
 
            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();
        }
    }

Focus on this line of code:

final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
      continue ;
}

Here we find the core method to solve this problem: getItemPosition(). The official explanation for getItemPosition() is:

Called when the host view is attempting to determine if an item's position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed orPOSITION_NONE if the item is no longer present in the adapter.

The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.

This means that if the location of the item does not change, position "unchanged" will be returned. If the position [none] is returned, the item in the position no longer exists. The default implementation is to assume that the location of the item will never change, and return position "unchanged"

3, Solutions

According to the above analysis, we can try to modify the writing method of the adapter, and override the getItemPosition() method. When notifyDataSetChanged is called, the getItemPosition method returns the position [none] artificially, so as to force the Viewpager to redraw all items.

@Override
public int getItemPosition(Object object) {
    return POSITION_NONE;
}

 

 

 

 

reference material: https://www.cnblogs.com/cheneasternsun/p/6017012.html

Tags: Android Mobile

Posted on Tue, 11 Feb 2020 00:42:26 -0500 by a94060