SoFunction
Updated on 2025-04-07

Detailed explanation of ViewPager reordering and updating instances in Android

Detailed explanation of ViewPager reordering and updating instances in Android

There is a column subscription function in recent projects. After changing the column order, you need to update the ViewPager. Similar to the channel management of NetEase News.

After reordering, I called the notifyDataSetChanged method of PagerAdapter and found that the ViewPager was not updated. So I started tracking the source code. After calling the notifyDataSetChanged method of PagerAdapter, the dataSetChanged method of Viewpager will be triggered.

 void dataSetChanged() {
    // This method only gets called if our observer is attached, so mAdapter is non-null.

    final int adapterCount = ();
    mExpectedAdapterCount = adapterCount;
    boolean needPopulate = () < mOffscreenPageLimit * 2 + 1 &&
        () < adapterCount;
    int newCurrItem = mCurItem;

    boolean isUpdating = false;
    for (int i = 0; i < (); i++) {
      final ItemInfo ii = (i);
      final int newPos = ();

      if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue;
      }

      if (newPos == PagerAdapter.POSITION_NONE) {
        (i);
        i--;

        if (!isUpdating) {
          (this);
          isUpdating = true;
        }

        (this, , );
        needPopulate = true;

        if (mCurItem == ) {
          // Keep the current item in the valid range
          newCurrItem = (0, (mCurItem, adapterCount - 1));
          needPopulate = true;
        }
        continue;
      }

      if ( != newPos) {
        if ( == mCurItem) {
          // Our current item changed position. Follow it.
          newCurrItem = newPos;
        }

         = newPos;
        needPopulate = true;
      }
    }

    if (isUpdating) {
      (this);
    }

    (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) ();
        if (!) {
           = ;
        }
      }

      setCurrentItemInternal(newCurrItem, false, true);
      requestLayout();
    }
  }

Through the source code, when data update occurs, ViewPager will call to determine whether the current page has changed. If the current page has not changed, it will return POSITION_UNCHANGED. If the order of the current page changes, it will return a new index. If the current page does not exist, it will return POSITION_NONE and remove the current page and update the current page.

Next, check the getItemPosition method of ViewPagerAdapter

 public int getItemPosition(Object object) {
    return POSITION_UNCHANGED;
  }

It is found that the default return of POSITION_UNCHANGED, which is why our ViewPager has not been updated. There are many solutions online, one of which is to directly rewrite getItemPosition and return POSITION_NONE directly. I also tried using it, but found that it was useless and the data was still not updated. Later I found that my Adapter inherited the FragmentPagerAdapter. The FragmentPagerAdapter comes with a cache policy, check its instantiateItem method.

 @Override
  public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
      mCurTransaction = ();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName((), itemId);
    Fragment fragment = (name);
    if (fragment != null) {
      if (DEBUG) (TAG, "Attaching item #" + itemId + ": f=" + fragment);
      (fragment);
    } else {
      fragment = getItem(position);
      if (DEBUG) (TAG, "Adding item #" + itemId + ": f=" + fragment);
      ((), fragment,
          makeFragmentName((), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
      (false);
      (false);
    }

    return fragment;
  }

We can find that the FragmentPagerAdapter manages the Fragment cache through its internal FragmentManager, and each Fragment is separated by name, and the name is generated by makeFragmentName. We check the makeFragmentName method

 private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
  }

A very simple string splicing is one of the id of the Viewpager and the other is generated by the getItemId method. The getItemId method is simpler to directly return position, which is why we cannot update the data.

  /**
   * Return a unique identifier for the item at the given position.
   *
   * <p>The default implementation returns the given position.
   * Subclasses should override this method if the positions of items can change.</p>
   *
   * @param position Position within this adapter
   * @return Unique identifier for the item at position
   */
  public long getItemId(int position) {
    return position;
  }

After knowing the reason, I started to transform the Adapter. First, generate a unique ID for each channel. My approach is to use a map to save the corresponding relationship between the channel name and the ID, and use a List to save the previous Position order. Remember to initialize it in notifyDataSetChanged. Since the List saves the previous Position, it needs to be added after the update is completed.

int id=1;
  Map<String,Integer> IdsMap=new HashMap<>();
  List<String> preIds=new ArrayList<>();
 @Override
  public void notifyDataSetChanged() {
    for(MenuInfo info:data){
      if(!(())){
        ((),id++);
      }
    }
    ();
    ();
    int size=getCount();
    for(int i=0;i<size;i++){
      ((String) getPageTitle(i));
    }
  }

Then rewrite getItemPosition

 @Override
  public int getItemPosition(Object object) {
    ItemFragment fragment= (ItemFragment) object;
    String title=();
    int preId = (());
    int newId=-1;
    int i=0;
    int size=getCount();
    for(;i<size;i++){
      if(getPageTitle(i).equals(())){
        newId=i;
        break;
      }
    }
    if(newId!=-1&&newId==preId){
      ("zgh","title="+title+" POSITION_UNCHANGED");
      return POSITION_UNCHANGED;
    }
    if(newId!=-1){
      ("zgh","title="+title+" newId="+newId);
      return newId;
    }
    ("zgh","title="+title+" POSITION_NONE");
    return POSITION_NONE;
  }

And getItemId

 @Override
  public long getItemId(int position) {
    return (getPageTitle(position));
  }

Complete code

package ;

import ;
import .;
import .;
import .;
import ;
import ;

import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;
import ;

/**
 * Created by zhuguohui on 2016/5/12.
 */
public class MenuInfoPageAdapter extends FragmentPagerAdapter {
  List<MenuInfo> data;
  int id=1;
  Map<String,Integer> IdsMap=new HashMap<>();
  List<String> preIds=new ArrayList<>();
  public MenuInfoPageAdapter(FragmentManager manager, List<MenuInfo> data){
    super(manager);
    = data==null? new ArrayList<MenuInfo>() :data;

  }

  @Override
  public int getCount() {
    return ();
  }


  @Override
  public Fragment getItem(int position) {
    ItemFragment fragment=new ItemFragment();
    Bundle bundle=new Bundle();
    (TRSUrlFragment.KEY_URL,(position).getUrl());
    (TRSUrlFragment.KEY_TITLE, (position).getTitle());
    (bundle);
    return fragment;
  }

  @Override
  public CharSequence getPageTitle(int position) {
    return (position).getTitle();
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
    return (container, position);
  }

  @Override
  public long getItemId(int position) {
    return (getPageTitle(position));
  }

  @Override
  public int getItemPosition(Object object) {
    ItemFragment fragment= (ItemFragment) object;
    String title=();
    int preId = (());
    int newId=-1;
    int i=0;
    int size=getCount();
    for(;i<size;i++){
      if(getPageTitle(i).equals(())){
        newId=i;
        break;
      }
    }
    if(newId!=-1&&newId==preId){
      ("zgh","title="+title+" POSITION_UNCHANGED");
      return POSITION_UNCHANGED;
    }
    if(newId!=-1){
      ("zgh","title="+title+" newId="+newId);
      return newId;
    }
    ("zgh","title="+title+" POSITION_NONE");
    return POSITION_NONE;
  }

  @Override
  public void notifyDataSetChanged() {
    for(MenuInfo info:data){
      if(!(())){
        ((),id++);
      }
    }
    ();
    ();
    int size=getCount();
    for(int i=0;i<size;i++){
      ((String) getPageTitle(i));
    }
  }
}

Thank you for reading, I hope it can help you. Thank you for your support for this site!