You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
android-notes/blogs/RecyclerView.md

706 lines
28 KiB

6 years ago
---
RecyclerView
---
#### 目录
1. 思维导图
2. 基本使用
- 复杂布局的实现、添加头布局、尾布局
- 上拉刷新、下拉加载
3. 高级玩法
- LayoutManager
- ItemDecoration
- ItemAnimator
- ItemTouchHelper
- 结合 SnapHelper
- 万能 Adapter
4. 源码分析系列
- DefaultItemAnimator
- 缓存机制
- ListView 的 RecycleBin
- RecyclerView 的 Recycler
- 局部刷新
- 两者区别
5. 其他
- 扩展 RecyclerView
- 嵌套滑动
- 与 ListView 对比
- RecyclerView 优缺点
- ListView 优缺点
6. 参考
#### 思维导图
#### 基本使用
##### 复杂布局的实现
其实就是多个 ItemType 的场景,实现起来也很简单。定义多个 ItemTpye 和 ViewHolder,在 onCreateViewHolder 中通过 itemType 返回不同的 ViewHolder,onBindViewHolder 时根据 ViewHolder 的不同在设置不同的数据,完事。
这里想说一下头布局和尾布局的实现方式,其实也可以用上面的方式解决,但是我们可以用一种更加优雅的方式解决,那就是使用装饰者模式来实现扩展。
```java
public class BaseRvAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_NORMAL = 1;
private static final int TYPE_FOOTER = 2;
private BaseRvAdapter mBaseRvAdapter;
private View mHeaderView;
private View mFooterView;
public BaseRvAdapterWrapper(BaseRvAdapter baseRvAdapter) {
mBaseRvAdapter = baseRvAdapter;
}
public void setHeaderView(View headerView) {
mHeaderView = headerView;
}
public void setFooterView(View footerView) {
mFooterView = footerView;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
if (i == TYPE_HEADER) {
return new RecyclerView.ViewHolder(mHeaderView) {
};
} else if (i == TYPE_NORMAL) {
return mBaseRvAdapter.onCreateViewHolder(viewGroup, i);
} else {
return new RecyclerView.ViewHolder(mFooterView) {
};
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
if (i == 0 || i == mBaseRvAdapter.getItemCount() + 1) {
return;
} else {
mBaseRvAdapter.onBindViewHolder((BaseRvAdapter.MyViewHolder) viewHolder, i - 1);
}
}
@Override
public int getItemCount() {
return mBaseRvAdapter.getItemCount() + 2;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
} else if (position == mBaseRvAdapter.getItemCount() + 1) {
return TYPE_FOOTER;
} else {
return TYPE_NORMAL;
}
}
}
```
BaseRvAdapter 就是我们平常写的最基本的 Adapter,ItemView 都一样的时候。
关于复杂布局,其实也可以参考阿里开源的 V-Layout,不过它是内置了很多自定义 LayoutManager。
##### 上拉刷新、下拉加载
#### 高级玩法
##### LayoutManager
常见实现类(LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager),下面列出 LayoutManager 常用的 API:
```java
canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置
setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向
findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置
```
自定义 LayoutManager:
##### ItemDecoration
RecyclerView 通过 addItemDecoration 方法添加 Item 之间的分割线,高版本 Android 已经提供默认实现,想改的话只需要 copy 一下这个类改一下 Drawable 即可。
```java
public class DividerItemDecoration extends ItemDecoration {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private static final String TAG = "DividerItem";
private static final int[] ATTRS = new int[]{16843284};
private Drawable mDivider;
private int mOrientation;
private final Rect mBounds = new Rect();
public DividerItemDecoration(Context context, int orientation) {
TypedArray a = context.obtainStyledAttributes(ATTRS);
this.mDivider = a.getDrawable(0);
if (this.mDivider == null) {
Log.w("DividerItem", "@android:attr/listDivider was not set in the theme used for this DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
this.setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != 0 && orientation != 1) {
throw new IllegalArgumentException("Invalid orientation. It should be either HORIZONTAL or VERTICAL");
} else {
this.mOrientation = orientation;
}
}
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
} else {
this.mDivider = drawable;
}
}
public void onDraw(Canvas c, RecyclerView parent, State state) {
if (parent.getLayoutManager() != null && this.mDivider != null) {
if (this.mOrientation == 1) {
this.drawVertical(c, parent);
} else {
this.drawHorizontal(c, parent);
}
}
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
int left;
int right;
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, this.mBounds);
int bottom = this.mBounds.bottom + Math.round(child.getTranslationY());
int top = bottom - this.mDivider.getIntrinsicHeight();
this.mDivider.setBounds(left, top, right, bottom);
this.mDivider.draw(canvas);
}
canvas.restore();
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
int top;
int bottom;
if (parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom);
} else {
top = 0;
bottom = parent.getHeight();
}
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, this.mBounds);
int right = this.mBounds.right + Math.round(child.getTranslationX());
int left = right - this.mDivider.getIntrinsicWidth();
this.mDivider.setBounds(left, top, right, bottom);
this.mDivider.draw(canvas);
}
canvas.restore();
}
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
if (this.mDivider == null) {
outRect.set(0, 0, 0, 0);
} else {
if (this.mOrientation == 1) {
outRect.set(0, 0, 0, this.mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, this.mDivider.getIntrinsicWidth(), 0);
}
}
}
}
```
[DividerItemDecoration](https://android.googlesource.com/platform/development/+/cc33d7e/samples/Support7Demos/src/com/example/android/supportv7/widget/decorator/DividerItemDecoration.java)
##### ItemAnimator
RecyclerView 通过 setItemAnimator 方法设置添加、删除、移动、改变的动画效果。
RecyclerView 提供了默认的 ItemAnimator 实现类:DefaultItemAnimator。
DefaultItemAnimator 是继承至 SimpleItemAnimator 类,而 SimpleItemAnimator 继承至 ItemAnimator。在自定义 ItemAnimator 时只需要继承 SimpleItemAnimator 即可,因为该类提供的 API 更加清晰易懂。
继承 SimpleItemAnimator 需要实现的方法有:
```java
//在 SimpleItemAnimator 中定义的抽象方法
//当 Item 移除时调用
public abstract boolean animateRemove(ViewHolder var1);
//当 Item 添加时调用
public abstract boolean animateAdd(ViewHolder var1);
//当 Item 移动时调用
public abstract boolean animateMove(ViewHolder var1, int var2, int var3, int var4, int var5);
//当显式调用 notifyItemChanged() 或 notifyDataSetChanged() 时被调用
public abstract boolean animateChange(ViewHolder var1, ViewHolder var2, int var3, int var4, int var5, int var6);
//在 ItemAnimator 中定义的抽象方法
//执行动画
//RecyclerView 动画的执行方式并不是立即执行,而是每帧执行一次,
//比如两帧之间添加了多个 Item,则会将这些要执行的动画 Pending 住,保存在成员变量中
//等到下一帧一起执行,该方法的执行前提是前面 animatorXxx 返回 true
public abstract void runPendingAnimations();
//当 Item 动画执行完毕时调用
public abstract void endAnimation(@NonNull RecyclerView.ViewHolder var1);
//当所有动画执行完毕时调用
public abstract void endAnimations();
//是否有动画在执行或者将要执行
public abstract boolean isRunning();
```
6 years ago
但是即使这样,需要实现的方法还是一大堆,并且还的自己去写动画的添加、回收等等。所以我建议直接看 [**recyclerview-animators**](https://github.com/wasabeef/recyclerview-animators) 中的 BaseItemAnimator,然后继承它只需要重写动画逻辑就好了,不必在考虑繁琐的动画添加、回收等工作。
6 years ago
##### ItemTouchHelper
6 years ago
ItemTouchHelper 能用来实现 RecyclerView Item 的上下拖拽以及滑动删除功能,实现起来可以说是究极简单。
分为两步:
1. 实现 ItemTouchHelper.Callback 回调
2. 把 ItemTouchHelper 绑定到 RecyclerView 上
第一步,多说无益:
```java
public class DefaultItemTouchHelper<T> extends ItemTouchHelper.Callback {
private BaseRvAdapterWrapper mBaseRvAdapterWrapper;
private List<T> mList;
public DefaultItemTouchHelper(BaseRvAdapterWrapper baseRvAdapterWrapper, List<T> list) {
mBaseRvAdapterWrapper = baseRvAdapterWrapper;
mList = list;
}
/**
* 设置支持滑动、拖拽的方向
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //上下拖拽
int swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; //左右滑动
return makeMovementFlags(dragFlag, swipeFlag);
}
/**
* 拖拽时回调
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
int form = viewHolder.getAdapterPosition();
int to = viewHolder1.getAdapterPosition();
Collections.swap(mList, form, to);
mBaseRvAdapterWrapper.notifyItemMoved(form, to);
return true;
}
/**
* 滑动时回调
*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
int position = viewHolder.getAdapterPosition();
mList.remove(position);
mBaseRvAdapterWrapper.notifyItemRemoved(position);
}
/**
* 状态改变时回调
*/
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.parseColor("#ffffff")); //设置拖拽和侧滑时的背景色
}
}
/**
* 拖拽或滑动完成之后回调
*/
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(Color.parseColor("#FFFFFF"));
}
/**
* 如果想自定义动画,可以重写这个方法
* 根据偏移量来设置
*/
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
```
第二步:
```java
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DefaultItemTouchHelper<>(mBaseRvAdapterWrapper, mData));
itemTouchHelper.attachToRecyclerView(mRecyclerView);
```
OK,完事。
6 years ago
##### 结合 SnapHelper
6 years ago
SnapHelp 能够辅助 RecyclerView 在滚动结束时将 Item 对齐到某个位置。
SnapHelp 是一个抽象类,Android 提供了 LinearSnapHelper,可以让 RecyclerView 滚动停止时 Item 停留在中间位置,又提供了 PagerSnapHelper,可以让 RecyclerView 像 ViewPager 一样的效果,一次只能滑动一个,并且 Item 居中显示,和 LinearSnapHelper 的区别在于 LinearSnapHelper 支持惯性滑动,所以一次能滑动多个。
使用起来极其简单:
```java
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();
linearSnapHelper.attachToRecyclerView(mRecyclerView);
PagerSnapHelper pagerSnapHelper = new PagerSnapHelper();
pagerSnapHelper.attachToRecyclerView(mRecyclerView);
```
在分析 SnapHelper 原理之前,先来了解一下 Fling 概念。Fling 操作从手指离开屏幕瞬间被触发,在停止滚动时结束,即惯性滑动。
SnapHelpr 是一个抽象类,它有三个抽象方法:
```java
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int i, int i1) {
return 0;
}
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
return null;
}
@Nullable
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View view) {
return new int[0];
}
```
findTargetSnapPosition 方法会根据触发 Fling 操作的速率来找到 RecyclerView 需要滚动到哪个位置,该位置对应的 ItemView 就是那个需要对齐的列表项。我们把这个位置称为 targetSnapPosition,对应的 View 称为 targetSnapView,如果找不到 targetSnapPosition,就返回 RecyclerView.NO_POSITION。
findSnapView 方法会找到当前 LayoutManager 最接近对齐位置的那个 ItemView,该 View 称为 SnapView,对应的 position 称为 SnapPosition。如果返回 null,则表示没有要对齐的 View,也就不会做对齐调整。
calculateDistanceToFinalSnap 方法会计算需要对齐的 ItemView 与目标 View 之间的距离,返回一个大小为二的 int 数组,分别对应 x 轴和 y 轴方向上的距离。
![](https://github.com/Omooo/Android-Notes/blob/master/images/SnapView.png)
6 years ago
##### 万能 Adapter
#### 源码分析系列
##### DefaultItemAnimator
DefaultItemAnimator 类是 Android 提供的默认的动画类。首先看一下其成员变量:
```java
//...
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList();
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList();
ArrayList<ViewHolder> mAddAnimations = new ArrayList();
ArrayList<ViewHolder> mRemoveAnimations = new ArrayList();
```
这里只列举了移除和添加 Item 时所需的动画,可以看出动画的执行单位是 ViewHolder,并且可以分为两类,一类是保存将要执行的动画,一类是保存当前正在执行的动画。
然后在看 animateRemove 和 animateAdd 方法:
```java
public boolean animateRemove(ViewHolder holder) {
this.resetAnimation(holder);
this.mPendingRemovals.add(holder);
return true;
}
public boolean animateAdd(ViewHolder holder) {
this.resetAnimation(holder);
holder.itemView.setAlpha(0.0F);
this.mPendingAdditions.add(holder);
return true;
}
```
这两个方法是在移除和添加 Item 的时候会调用的方法,就是用 mPengdingXxx 来保存即将要执行的动画,值得注意的是,添加 Add 动画的时候会先把该 Item 置为全透明,这和我们设置 DefaultItemAnimator 后所看的是一致的。
接着就是 runPendingAnimations 方法,这个方法前面说过,就是将要执行的动画集合。源码如下:
```java
public void runPendingAnimations() {
boolean removalsPending = !this.mPendingRemovals.isEmpty();
boolean movesPending = !this.mPendingMoves.isEmpty();
boolean changesPending = !this.mPendingChanges.isEmpty();
boolean additionsPending = !this.mPendingAdditions.isEmpty();
if (removalsPending || movesPending || additionsPending || changesPending) {
//移除 Item 动画
Iterator var5 = this.mPendingRemovals.iterator();
while(var5.hasNext()) {
ViewHolder holder = (ViewHolder)var5.next();
this.animateRemoveImpl(holder);
}
this.mPendingRemovals.clear();
final ArrayList additions;
Runnable adder;
//移动 Item 动画
if (movesPending) {
additions = new ArrayList();
additions.addAll(this.mPendingMoves);
this.mMovesList.add(additions);
this.mPendingMoves.clear();
adder = new Runnable() {
public void run() {
Iterator var1 = additions.iterator();
while(var1.hasNext()) {
DefaultItemAnimator.MoveInfo moveInfo = (DefaultItemAnimator.MoveInfo)var1.next();
DefaultItemAnimator.this.animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY);
}
additions.clear();
DefaultItemAnimator.this.mMovesList.remove(additions);
}
};
if (removalsPending) {
View view = ((DefaultItemAnimator.MoveInfo)additions.get(0)).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, adder, this.getRemoveDuration());
} else {
adder.run();
}
}
if (changesPending) {
additions = new ArrayList();
additions.addAll(this.mPendingChanges);
this.mChangesList.add(additions);
this.mPendingChanges.clear();
adder = new Runnable() {
public void run() {
Iterator var1 = additions.iterator();
while(var1.hasNext()) {
DefaultItemAnimator.ChangeInfo change = (DefaultItemAnimator.ChangeInfo)var1.next();
DefaultItemAnimator.this.animateChangeImpl(change);
}
additions.clear();
DefaultItemAnimator.this.mChangesList.remove(additions);
}
};
if (removalsPending) {
ViewHolder holder = ((DefaultItemAnimator.ChangeInfo)additions.get(0)).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, adder, this.getRemoveDuration());
} else {
adder.run();
}
}
if (additionsPending) {
additions = new ArrayList();
additions.addAll(this.mPendingAdditions);
this.mAdditionsList.add(additions);
this.mPendingAdditions.clear();
adder = new Runnable() {
public void run() {
Iterator var1 = additions.iterator();
while(var1.hasNext()) {
ViewHolder holder = (ViewHolder)var1.next();
DefaultItemAnimator.this.animateAddImpl(holder);
}
additions.clear();
DefaultItemAnimator.this.mAdditionsList.remove(additions);
}
};
if (!removalsPending && !movesPending && !changesPending) {
adder.run();
} else {
long removeDuration = removalsPending ? this.getRemoveDuration() : 0L;
long moveDuration = movesPending ? this.getMoveDuration() : 0L;
long changeDuration = changesPending ? this.getChangeDuration() : 0L;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = ((ViewHolder)additions.get(0)).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
}
}
}
}
```
可以看出 Remove 动画的是最先执行的,然后依次是 Move 动画、Change 动画、Add 动画。
然后通过 Iterator 迭代器遍历 List 执行动画,显然真正执行动画的逻辑肯定就在 animateXxxImpl 方法里面了。
但是细心的你肯定会发现,为什么除了 Remove 动画,其他都需要在 Runnable 里构造执行体呢?
从 Runnable 的 run 方法的执行条件可以看出,如果有 Remove 动画,那么其他动画必须延时执行,如果没有,那就立即执行。
然后就可以看看动画真正的实现了,这里就拿 Add 动画为例,即 animateAddImpl:
```java
void animateAddImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
this.mAddAnimations.add(holder);
animation.alpha(1.0F).setDuration(this.getAddDuration()).setListener(new AnimatorListenerAdapter() {
public void onAnimationStart(Animator animator) {
DefaultItemAnimator.this.dispatchAddStarting(holder);
}
public void onAnimationCancel(Animator animator) {
view.setAlpha(1.0F);
}
public void onAnimationEnd(Animator animator) {
animation.setListener((AnimatorListener)null);
DefaultItemAnimator.this.dispatchAddFinished(holder);
DefaultItemAnimator.this.mAddAnimations.remove(holder);
DefaultItemAnimator.this.dispatchFinishedWhenDone();
}
}).start();
}
```
可以看到,动画真正的实现是通过 [ViewPropertyAnimator](https://developer.android.com/reference/android/view/ViewPropertyAnimator?hl=zh-cn) 来实现的,前面说到,在 Add 动画的时候会把该 ViewHolder 设置为全透明,所以这是一个透明度渐变的过程,getAddDuration 是写死的 120 毫秒,而且动画是不可逆的。
那我们再想想为什么不用 ObjectAnimator 来实现呢?
按照官方的说法,此类可以为多个同时动画提供更好的性能,而且在多个动画同时执行,并不会导致某个动画属性执行失效,并且使用更加简单。
此类不是由调用者构造的,而是通过 View.animate() 返回的对应 View 的 ViewPropertyAnimator 对象的引用。这给我们使用 AnimatorSet 又带来了一种新选择,get 。
6 years ago
上面都是使用 mPengdingXxx,那 mXxxAnimations 有什么用呢?我们只看到它在动画执行前添加一个 ViewHolder,动画执行完毕之后又移除这个 ViewHoler。
其实它的作用很简单,就是判断动画是否正在在执行:
```java
public boolean isRunning() {
return !this.mPendingAdditions.isEmpty() || !this.mPendingChanges.isEmpty() || !this.mPendingMoves.isEmpty() || !this.mPendingRemovals.isEmpty() || !this.mMoveAnimations.isEmpty() || !this.mRemoveAnimations.isEmpty() || !this.mAddAnimations.isEmpty() || !this.mChangeAnimations.isEmpty() || !this.mMovesList.isEmpty() || !this.mAdditionsList.isEmpty() || !this.mChangesList.isEmpty();
}
```
6 years ago
##### 总结:
6 years ago
1. 动画的执行是有优先级之分的,Remove > Move > Change > Add
2. 我们重写的 animateXxx 等方法只是把将要执行的动画添加到执行队列( List )中,然后在 runPendingAnimations 一并执行
3. 真正的动画效果是通过 ViewPropertyAnimator 来实现的,它在多个动画同时执行时表现出更好的性能
##### 缓存机制
RecyclerView 比 ListView 多两级缓存,支持多个离 ItemView 缓存,支持开发者自定义缓存处理逻辑。支持所有的 RecyclerView 共用同一个 RecyclerViewPool。
具体来说:
1.层级不同
ListView 两级缓存:
| | 是否需要回调 createView | 是否需要回调 bindView | 生命周期 | 备注 |
| ------------ | ----------------------- | --------------------- | ---------------------------------------------------------- | ------------------------------ |
| mActiveViews | 否 | 否 | onLayout 函数周期内 | 用于屏幕内 ItemView 的快速重用 |
| mScrapViews | 否 | 是 | 与 mAdapter 一致,当 mAdapter 更改时,mScrapViews 即被清空 | |
RecyclerView 四级缓存:
| | 是否需要 createView | 是否需要 bindView | 生命周期 | 备注 |
| ------------------- | ------------------- | ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| mAttachedScrap | 否 | 否 | onLayout 函数周期内 | 用于屏幕内 ItemView 的快速重用 |
| mCacheViews | 否 | 否 | 与 mAdapter 一致,当 mAdapter 被更改时,mCacheViews 即被缓存至 mRecyclerPool | 默认上限为二,即缓存屏幕外两个 ItemView |
| mViewCacheExtension | | | | 不直接使用,需要用户定制,默认不实现 |
| mRecyclerPool | 否 | 是 | 与自身生命周期一致,不再被引用时即被释放 | 默认上限为五,但是技术上可以实现所有 RecyclerViewPool共用同一个 |
2.缓存对象不同
RecyclerView 缓存的对象为 ViewHolder,ListView 缓存 View。
#### 其他
##### 扩展 RecyclerView
##### 嵌套滑动
##### 与 ListView 对比
RecyclerView 相比 ListView,有一些明显的优点:
1. 默认已经实现了 View 的复用,而且复用机制更加完善
2. 支持局部刷新
3. Item 添加、删除动画,Item 实现拖拽、侧滑删除等
4. 更灵活的 LayoutManager
当然,ListView 相比 RecyclerView 也有优点:
1. addHeaderView、addFooterView 添加头视图、尾视图
2. 通过 android:divider 设置自定义分割线
3. setOnItemClickListener、setOnItemLongClickListener 设置点击事件以及长按事件
#### 参考
[让你明明白白的使用RecyclerView——SnapHelper详解](https://www.jianshu.com/p/e54db232df62)
[Android ListView 与 RecyclerView 对比浅析--缓存机制](https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653578065&idx=2&sn=25e64a8bb7b5934cf0ce2e49549a80d6&chksm=84b3b156b3c43840061c28869671da915a25cf3be54891f040a3532e1bb17f9d32e244b79e3f&scene=21#wechat_redirect)