大家都用过ViewPager吧😄,使用ViewPager时,需要给它配一个Adapter,通常我们要继承下面三个Adapter来,分别是:
———>最基本的PagerAdapter
——————>继承PagerAdapter的FragmentPagerAdapter
——————>继承PagerAdapter的FragmentStatePagerAdapter
那么这三个Adapter有什么区别,我们应该怎么选择呢?
首先,我们知道如果继承了PagerAdapter,需要我们实现下面两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  abstract  int  getCount () ;public  Object instantiateItem (ViewGroup container, int  position)  {    return  instantiateItem((View) container, position); } 
第一个是要告诉ViewPager一共有几个页面,第二个则是ViewPager需要加载页面了,你需要根据position创建不同的页面对象,这里通常是View对象。 
当然,如果你的ViewPager中内容比较复杂,需要用Fragment来自动管理其生命周期,那么可以使用FragmentPagerAdapter和FragmentStatePagerAdapter中的一种,那么他俩有什么区别呢?
首先我们看一下FragmentPagerAdapter的instantiateItem和destroyItem方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40  	@Override     public  Object instantiateItem (ViewGroup container, int  position)  {        ...        final  long  itemId  =  getItemId(position);                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));        }       ...        return  fragment;    }    @Override     public  void  destroyItem (ViewGroup container, int  position, Object object)  {        if  (mCurTransaction == null ) {            mCurTransaction = mFragmentManager.beginTransaction();        }        if  (DEBUG) Log.v(TAG, "Detaching item #"  + getItemId(position) + ": f="  + object                + " v="  + ((Fragment)object).getView());        mCurTransaction.detach((Fragment)object);    } public  long  getItemId (int  position)  {       return  position;    }    private  static  String makeFragmentName (int  viewId, long  id)  {        return  "android:switcher:"  + viewId + ":"  + id;    } 
可以看出instantiateItem方法在添加Fragment时,会带一个Fragment的名字,其实就是Tag:
1 mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); 
这个Tag是根据一定规则从makeFragmentName中获得的,这里唯一变化的就是id,默认的id可从getItemId(position)中看到,就是当前的position,保证了每个fragment的tag是唯一的。
当ViewPager通过instantiateItem获取Fragment的时候,会先根据之前设置的Tag找一下这个Fragment存不存在:
1 mFragmentManager.findFragmentByTag(name) 
根据获取到的Fragment有两种添加方式,如果根据Tag找到了之前存在的Fragment,就attach上去:
1 mCurTransaction.attach(fragment); 
如果没找到,那就把新创建的Fragment add上去:
1 mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); 
当不用这个Fragment的时,会调用destroyItem方法,可以看到这里是detach掉了Fragment,并没有销毁。
综上,一个页面只会创建一次,创建时根据当前的position给Fragment设置一个Tag,当不需要时只是把Fragmetn detach掉,并不会销毁,下次需要时通过Tag复用Fragment。
所以,如果你有大量的Fragment要展示,FragmentPagerAdapter会持有每一个Fragment不释放,最终走向OOM。
所以,无论使用PagerAdapter还是FragmentPagerAdapter,有多少页面,就会创建多少页面对象,页面很多的情况下,会非常占用内存,虽然这样,但是它们也有各自的应用场景,例如,App的首次安装的启动引导页面个数是固定的,而且如果比较复杂,通常做成Fragment,这时使用FragmentPagerAdapter较为合适;而如果页面中有需要自动轮播卡片的地方,则可以使用PagerAdapter实现,因为其页面一般都是View,结构简单,数量也是固定的。
那如果我需要做一个可以无限滑动的卡片,就需要用到FragmentStatePagerAdapter了,我们看一下FragmentStatePagerAdapter的instantiateItem和destroyItem方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public  abstract  Fragment getItem (int  position) ;@Override public  Object instantiateItem (ViewGroup container, int  position)  {    ...          if  (mCurTransaction == null ) {         mCurTransaction = mFragmentManager.beginTransaction();     }     Fragment  fragment  =  getItem(position);     while  (mFragments.size() <= position) {         mFragments.add(null );     }       	...     mCurTransaction.add(container.getId(), fragment);     return  fragment; } @Override public  void  destroyItem (ViewGroup container, int  position, Object object)  {    Fragment  fragment  =  (Fragment) object;     ...     mCurTransaction.remove(fragment); } 
可以看到,FragmentStatePagerAdapter在初始化页面时,通过getItem这个抽象方法来获得Fragment,并将其add到容器里,而不用的时候直接remove掉,这样就不会一直占用Fragment,可以实现无限滑动,唯一关心的就是在getItem中创建一个Fragment就可以了。
以上就是我对三种PagerAdapter的简单理解。在使用ViewPager的过程中,除了adapter的选择,还需要到了下面的问题。
2.常见的两个问题小析 1.notifyDataSetChanged()后页面没有刷新的问题 这个问题主要是由于当数据更新时,ViewPager会调用dataSetChanged方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void  dataSetChanged ()  {       	...     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) {                                  newCurrItem = Math.max(0 , Math.min(mCurItem, adapterCount - 1 ));                 needPopulate = true ;             }             continue ;         }      ...     }    ... } 
这个方法会调用adapter的getItemPosition()来获得object的位置情况是否发生变化,如果不发生变化就不更新了,如果而这个方法默认是返回的就是POSITION_UNCHANGED:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18   public  int  getItemPosition (Object object)  {    return  POSITION_UNCHANGED; } 
所以要想每次刷新都让页面更新,需要在adapter中重写getItemPosition方法,并返回POSITION_NONE就可以了:
1 2 3 4 5 @Override public  int  getItemPosition (Object object)  {    return  POSITION_NONE; } 
2.初始化数据时不会调用OnPageChangeListener 相信大家都遇到过这种情况,就是ViewPager初始化时并不会调用OnPageChangeListener,所以很多时候我们都是手动调用第一页的onPageSelected方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24   private  ViewPager.OnPageChangeListener  onPageChangeListener  =  new  ViewPager .OnPageChangeListener() {       @Override        public  void  onPageScrolled (int  position, float  positionOffset, int  positionOffsetPixels)  {       }       @Override        public  void  onPageSelected (int  position)  {       }       @Override        public  void  onPageScrollStateChanged (int  state)  {       }   };   public  void  OnCreate () {       ...       mViewPager.setOnPageChangeListener(onPageChangeListener); onPageChangeListener.onPageSelected(0 );         ...   } 
但是如果如果你使用的是FragmentPagerAdapter时,会发现在onPageSelected(0)时,会发现Fragment还没有创建成功,这时候会出现NPE(NullPointerException)。所以这时候可以这样写:
1 2 3 4 5 6 mViewPager.post(new  Runnable (){ @Override     public  void  run ()  {         onPageChangeListener.onPageSelected(0 );       } }); 
这样就会在viewpager的UI事件队列完成后处理onPageSelected方法的内容,这个时候Fragment已经好了。
但是,虽然这样,讲要进行的工作交给post会打乱同步时序,让要做的事充满了不确定性,我不知道它什么时候能调用onPageSelected方法。而且这样做也存在另外一个问题,当使用notifyDataSetChanged()刷新数据时,还是不会调用当前页的onPageSelected来更新当前页面,所以我更倾向于下面这种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public  class  MyPagerAdapter  extends  FragmentStatePagerAdapter  {    ...              private  Object lastPrimaryItem;          private  OnPageSwitchListener onPageSwitchListener;     public  MyPagerAdapter (FragmentManager fm, OnPageSwitchListener onPageSwitchListener)  {         super (fm);         this .onPageSwitchListener = onPageSwitchListener;     }          @Override      public  void  setPrimaryItem (ViewGroup container, int  position, Object object)  {         super .finishUpdate(container);         super .setPrimaryItem(container, position, object);         if  (object == lastPrimaryItem) {             return ;           }         lastPrimaryItem = object;         MyFragment  fragment  =  (MyFragment)object;         if  (onPageSwitchListener != null  && fragment != null ) {             onPageSwitchListener.onPageSwitch(position, fragment);         }     }     @Override      public  void  finishUpdate (ViewGroup container)  { 		              }          public  interface  OnPageSwitchListener {         void  onPageSwitch (int  position, MyFragment fragment) ;     } } class  MyActivity  extends  Activity  implements  MyPagerAdapter .OnPageSwitchListener{    @Override      public  void  onPageSwitch (int  position, MyFragment fragment)  {     	     } } 
简单说就是,PagerAdapter中有一个重要的方法叫setPrimaryItem,当一个页面显示的时候都会调用这个方法(多次),通过一些简单的处理,可以实现与mViewPager.setOnPageChangeListener()类似的功能,这样就不用每次调用onPageChangeListener.onPageSelected(0)或者使用Post操作了。
以上分析较为肤浅,有问题欢迎指正、补充,谢谢🙏
参考
[1] https://stackoverflow.com/questions/16074058/onpageselected-doesnt-work-for-first-page
[2] https://stackoverflow.com/questions/11794269/onpageselected-isnt-triggered-when-calling-setcurrentitem0