实现列表

ListView与RecyclerView的原理与应用

Posted by candy1126xx on April 14, 2017

ListView

ItemView的性质

ViewType
  • ListHeader和ListFooter为-2;
  • 不进行复用的标记为-1;
  • 其余 >= 0。
TransientState

如果1个ItemView正在执行动画,或者记录了用户的某个操作,那么可以调用View.setTransientState(true)把它标记为”是持有瞬时状态的”,那么它将不被复用。

ID

ItemView的Id应该取决于内容的Id。它的作用是作为ItemView的唯一标示,使标记为TransientState的ItemView在入屏时不需要重新创建。

AbsListView.RecyclerBin

AbsListView.RecyclerBin是复用ItemView功能的实现类。其重要成员变量如下:

  • mActiveViews(View[]):存放显示在屏幕上的ItemView的引用。
  • mCurrentScrap(ArrayList):当ViewType只有1种时,当ItemView出屏时存放它的引用。
  • mScrapViews(ArrayList[]):当ViewType多于1种时,当ItemView出屏时把它的引用存放在mScrapViews[ViewType]。
  • mSkippedScrap(ArrayList):当不进行复用的ItemView出屏时存放它的引用。
  • mTransientStateViewsById(LongSparseArray):当被标记为TransientState、且有Id的ItemView出屏时存放它的引用。键为Id。
  • mTransientStateViews(SparseArray):当被标记为TransientState、没有Id的ItemView出屏时存放它的引用。键为position。

复用ItemView

复用的ItemView会作为getView()的第二个参数。通过判断它是否为null,来决定是否需要新建ItemView。另外,还可使用ViewHolder来减少findViewById()的调用。

储存ItemView

当ListView显示第一屏或ItemView出屏时发生。

  1. 把ItemView的position记录到ItemView.mLayoutParams.scrapedFromPosition;
  2. 按不同情况把ItemView的引用存放到AbsListView.RecyclerBin的不同集合中。
复用ItemView

当ListView显示第一屏或ItemView入屏时发生。

  1. 先根据Id从mTransientStateViewsById、或根据position从mTransientStateViews取;如果取不到,向下执行;
  2. 获取该位置的ViewType;如果只有一种ViewType,从mCurrentScrap中取ItemView;否则从mScrapViews[ViewType]中取。
  3. 取的过程是,遍历mCurrentScrap或mScrapViews[ViewType];
    • 如果某个ItemView.mLayoutParams.itemId与该位置的getItemId()相同,那么返回这个ItemView;
    • 如果某个ItemView.mLayoutParams.scrapedFromPosition与该位置的position相同,那么返回这个ItemView;
    • 如果都没有,那么返回mCurrentScrap或mScrapViews[ViewType]的末项。
  4. 当是第一屏的时候,集合都是空的,那么会返回null。

RecyclerView

RecyclerView.ViewHolder

RecyclerView的复用单位是ViewHolder,即强制使用ViewHolder优化findViewById()频繁调用的问题;ViewHolder中不仅封装了ItemView及子View的引用,还封装了mItemId、mItemViewType、mPosition、mFlag等。

ID

Adapter的mHasStableIds和getItemId():认实现是mHasStableIds是flase,getItemId()返回NO_ID;这会影响回收时进行detach操作还是remove操作、缓存时使用id还是position作为标示。

ItemView的detach操作

把ViewHolder打上FLAG_TMP_DETACHED标记。从RecyclerView中detach ViewHolder对应的ItemView。如果ViewHolder没有在执行动画,把它加入mAttachScrap,否则加入mChangedScrap。

ItemView的remove操作

如果ViewHolder被标记为FLAG_INVALID、且没有在执行动画、且Adapter.mHasStableIds为false,那么在回收时将进行remove操作。从RecyclerView中remove ViewHolder对应的ItemView,然后判断mCachedScrap缓存是否满了,如果满了,把ViewHolder的引用储存到mRecyclerPoll,否则储存到mCachedScrap。

RecyclerView的四个缓存

  • mChangedScrap:缓存被detach的、正在执行动画的ViewHolder;
  • mAttachScrap:缓存被detach的、没在执行动画的ViewHolder;
  • mCachedScrap:当mCachedScrap中元素数量不足mViewCacheMax时,缓存被detach的ViewHolde;
  • mRecyclerPoll:当mCachedScrap满了时,缓存被detach的ViewHolde;

区别

RecyclerView比ListView更有优势,体现在:

  • 把布局逻辑抽象成LayoutManager,基于此可以实现多种布局;默认实现的有线性布局、网格布局、瀑布流布局。
  • 把ItemView的入场/出场动画逻辑抽象成ItemAnimator,基于此可以实现多种入场/出场动画。
  • 使用四级缓存,使滑动更流畅。
  • 强制使用ViewHolder,优化频繁调用findViewById()带来的性能问题。
  • 可以实现局部刷新。

优化卡顿

  • 优化布局嵌套,减少过度绘制区域;
  • 在滑动过程中暂停加载图片;
  • 避免在滑动监听、getView()、createViewHolder()、bindViewHolder()等方法中进行耗时操作;
  • 为ItemView/ViewHolder根据内容绑定Id,充分利用ListView/RecyclerView的缓存复用机制。