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!