结合ViewPager深入浅出PagerAdapter

mmiv9129 5年前
   <p>ViewPager 和 PagerAdapter 的关键方法</p>    <p><img src="https://simg.open-open.com/show/2b7ed35e6b2af9af231369f9df0a355f.png"></p>    <p style="text-align: center;">关联方法</p>    <p><strong>ViewPager:</strong></p>    <pre>  <code class="language-java">setAdapter() 设置适配器 ;  dataSetChanged() Adapter中数据变化时候的监听回调处理方法;  populate() ViewPager中填充页面item时候的处理方法</code></pre>    <p><strong>PagerAdapter:</strong></p>    <pre>  <code class="language-java">startUpdate()  Viewpager显示的页面数据有所改变的回调  finishUpdate() 页面数据改变的处理结束后的回调方法  instantiateItem() 初始化一个item数据的时候的回调  destroyItem() 销毁一个item数据的时候会回调  setPrimaryItem()设置好当前显示item后的回调  isViewFromObject()  View 是否和 Object有关联关系  getItemPosition() 获取当前数据对应的位置  getPageTitle() 获取当前页面对应的标题  getCount() 获取总的item数量  getPageWidth() 获取item页面相对于ViewPager宽度</code></pre>    <p><strong>setAdapter中的Adapter 方法调用</strong></p>    <pre>  <code class="language-java">public void setAdapter(PagerAdapter adapter) {    if (mAdapter != null) {      //取消之前的adapter数据监听      mAdapter.setViewPagerObserver(null);        //老的关联数据需要销毁,意味着有数据改变,所以回调startUpdate方法                       mAdapter.startUpdate(this);       //移除之前的数据      for (int i = 0; i < mItems.size(); i++) {          final ItemInfo ii = mItems.get(i);          //销毁之前的item          mAdapter.destroyItem(this, ii.position, ii.object);      }      //数据改变结束,回调finishUpdate方法      mAdapter.finishUpdate(this);      //清楚之前的一些缓存变量 省略。。。。  }    //重新设置初始化变量 。。。。。   final PagerAdapter oldAdapter = mAdapter;   。。。。。    // 如果之前有保留状态,这里恢复      if (mRestoredCurItem >= 0) {          mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);          setCurrentItemInternal(mRestoredCurItem, false, true);          mRestoredCurItem = -1;          mRestoredAdapterState = null;          mRestoredClassLoader = null;      } else if (!wasFirstLayout) {         //重新填充每个item          populate();      } else {          requestLayout();      }  }</code></pre>    <p>}</p>    <p><strong>populate</strong></p>    <p>简要的来说,populate方法就是在ViewPager的FrameLayout上面,填充上需要显示的item页面 。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a6df7984c0db9f7ecea345a0364ac2a5.png"></p>    <p>页面示例图</p>    <pre>  <code class="language-java">//省略。。。。  //viewpager 要填充item了,回调startUpdate方法  mAdapter.startUpdate(this);  //以下代码用来计算需要实例化的item位置及数量  final int pageLimit = mOffscreenPageLimit;  final int startPos = Math.max(0, mCurItem - pageLimit);  final int N = mAdapter.getCount();  final int endPos = Math.min(N-1, mCurItem + pageLimit);  //省略。。。。  //左边缓存页面的处理  for (int pos = mCurItem - 1; pos >= 0; pos--) {  //以前的左边缓存数量大于现在需要的左边缓存数量,需要考虑销毁  if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {      if (ii == null) {          break;      }      //之前的缓存的item没有在执行滚动动画,可以销毁      if (pos == ii.position && !ii.scrolling) {          //移除ViewPager中的item相关数据          mItems.remove(itemIndex);          //回调destroyItem方法,通知PagerAdapter处理相关的销毁动作          mAdapter.destroyItem(this, pos, ii.object);          //减小index,ii 重新赋值          itemIndex--;          curIndex--;          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;      }  //填充左边的item的时候,发现之前有实例化的item可以放到这个位置上,直接将item的宽度加在左边  } else if (ii != null && pos == ii.position) {      extraWidthLeft += ii.widthFactor;      itemIndex--;      ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;  } else {      //左边这个位置,之前没有过item实例化,需要根据位置实例化一个item与之关联,同时计算左边需要的宽度      ii = addNewItem(pos, itemIndex + 1);      extraWidthLeft += ii.widthFactor;      curIndex++;      ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;    }  }</code></pre>    <p><strong>以下是addNewItem的源码</strong></p>    <pre>  <code class="language-java">ItemInfo addNewItem(int position, int index) {  ItemInfo ii = new ItemInfo();  //item对应的位置 。数据刷新的时候,可能数据本身未变,但是位置改变了,就需要调整这个position  ii.position = position;  //调用Adapter的 instantiateItem方法,根据位置初始化一个item,并返回一个Object与之关联,类似于一个key  ii.object = mAdapter.instantiateItem(this, position);  //获取宽度当前页面item宽度  ii.widthFactor = mAdapter.getPageWidth(position);  //数据保存到mItems中  if (index < 0 || index >= mItems.size()) {      mItems.add(ii);  } else {      mItems.add(index, ii);  }  return ii;  }</code></pre>    <p><strong>dataSetChanged</strong></p>    <pre>  <code class="language-java">setAdapter的时候,Viewpager 会设置一个监听器到Adapter中,去监听数据改变,然后调用到dataSetChanged方法。    boolean isUpdating = false;  //遍历现有的缓存数据  for (int i = 0; i < mItems.size();i++)  {  final ItemInfo ii = mItems.get(i);  //获取每个item对应的position  。Viewpager里面缓存的时候老的Position,当数据发生改变的时候,  或许itemInfo 对应的位置发生了改变,所以需要通过Adapter重新获取  // 这里会调用Adapter 中的 getItemPosition方法  final int newPos = mAdapter.getItemPosition(ii.object);  //如果itemInfo对应的数据位置没有发生改变,继续处理其他的item  if (newPos == PagerAdapter.POSITION_UNCHANGED){             continue;  }  //如果itemInfo对应的数据,在新的数据集合中没有了,需要销毁itemInfo  if (newPos == PagerAdapter.POSITION_NONE) {      mItems.remove(i);      i--;      //告诉Adapter有数据的改变,对应的Adapter要处理相关工作      if (!isUpdating) {          mAdapter.startUpdate(this);          isUpdating = true;      }      //销毁一个位置的item      mAdapter.destroyItem(this, ii.position, ii.object);     //有数据改变,item的销毁,需要重新布局填充页面数据      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;  }  //当前数据对应的位置发生了改变,给itemInfo重新赋值position,重新布置页面  if (ii.position != newPos) {      if (ii.position == mCurItem) {          // Our current item changed position. Follow it.               newCurrItem = newPos;      }      ii.position = newPos;      needPopulate = true;   }  }  //如果之前有数据的改变,回调finishUpdate方法,表示处理结束  if (isUpdating) {  mAdapter.finishUpdate(this);  }  //按照位置,重新排序关联的itemInfo  Collections.sort(mItems, COMPARATOR);</code></pre>    <p>PagerAdapter中 notifyDataSetChanged不起作用的问题</p>    <p>PagerAdapter中调用notifyDataSetChanged方法,最终会调用到</p>    <p>dataSetChanged() 。在代码执行过程中,会重新获取新的数据中的位置,调用getItemPosition方法,如果位置未发生改变,就不做处理。</p>    <pre>  <code class="language-java">public int getItemPosition(Object object)     {       return POSITION_UNCHANGED;    }</code></pre>    <p>然后,getItemPosition方法返回值是默认值,不做处理。所以新的数据结构中,如果item数据发生了改变,需要重写这个方法,调整新数据集合中,原有的ItemInfo 对应的数据位置。</p>    <p>FragmentStatePagerAdapter 最官方的ViewPager示例</p>    <ul>     <li> <p>instantiateItem(ViewGroup container, int position)</p> <pre>  <code class="language-java">if (mFragments.size() > position) {  //Adapter 中保存了之前实例化过的Framgent,再次显示的时候,直接从缓存中获取  Fragment f = mFragments.get(position);  if (f != null) {    return f;  }}  //开启事务  if (mCurTransaction == null) {  mCurTransaction = mFragmentManager.beginTransaction();  }  //根据位置初始化一个item ,之后就不会初始化了,会从缓存中获取  Fragment fragment = getItem(position);  //如果之前的fragment状态中有保存,恢复状态  if (mSavedState.size() > position) {  Fragment.SavedState fss = mSavedState.get(position);    if (fss != null) {  fragment.setInitialSavedState(fss);  }}  //保证fragment状态数量和fragment数量一致,没有状态的设置null  while (mFragments.size() <= position) {  mFragments.add(null);  }  //设置不可见  fragment.setMenuVisibility(false);  fragment.setUserVisibleHint(false);  mFragments.set(position, fragment);  //将fragment添加到Manager中进行管理  mCurTransaction.add(container.getId(), fragment);  return fragment;</code></pre> </li>     <li> <p>destroyItem()</p> <pre>  <code class="language-java">//由于instantiateItem方法返回值是Fragment,所以这里可以强转  Fragment fragment = (Fragment) object;  if (mCurTransaction == null) {  mCurTransaction = mFragmentManager.beginTransaction();  }  //mSavedState中填满null数据  while (mSavedState.size() <= position) {              mSavedState.add(null);  }  //之前有add过的fragment状态保存到mSavedState中  mSavedState.set(position, fragment.isAdded()?mFragmentManager.saveFragmentInstanceState(fragment) :null);  //mFragments移除保存的fragment实例  mFragments.set(position, null);  //manager中接触对fragment的管理  mCurTransaction.remove(fragment);</code></pre> </li>     <li> <p>setPrimaryItem()</p> <pre>  <code class="language-java">Fragment fragment = (Fragment)object;  if (fragment != mCurrentPrimaryItem) ;  //之前的显示的fragment设置不可见  if (mCurrentPrimaryItem != null) {  mCurrentPrimaryItem.setMenuVisibility(false);           mCurrentPrimaryItem.setUserVisibleHint(false);  }  //当前fragment设置显示状态为可见   if (fragment != null) {    fragment.setMenuVisibility(true);               fragment.setUserVisibleHint(true);   }   mCurrentPrimaryItem = fragment;   }</code></pre> </li>     <li> <p>finishUpdate</p> <pre>  <code class="language-java">if (mCurTransaction != null)   {//提交事务  mCurTransaction.commitNowAllowingStateLoss();  mCurTransaction = null;  }</code></pre> </li>    </ul>    <p>总结一下FragmentStatePagerAdapter中的调用逻辑</p>    <ul>     <li>instantiateItem 或者 destroyItem 做 add 或者 remove操作</li>     <li>finishUpdate 中做事务的提交</li>     <li>setPrimaryItem 中做Fragment的显示隐藏控制,标志当前显示fragment</li>     <li>Viewpager 不做Fragment的管理,以及View的显示,这些都是通过FragmentManager的事务来处理的</li>     <li>如果要做fragment数据的刷新,位置改变,需要 重写getItemPosition方法,同时控制 instantiateItem中对于缓存的获取,mFragments.get(position) 自定义一套缓存获取规则。</li>    </ul>    <p> </p>    <p>来自:http://www.jianshu.com/p/91e2b38940d2</p>    <p> </p>