# RecyclerView
# 基本结构
**RecyclerView:**RecyclerView本身是一个ViewGroup, 只是作为一个父view来填充子view。在这个类中有内部类LayoutManager、Recycler、ViewHolder、Adapter分别实现他的布局管理和数据复用、UI展示。
**LayoutManager:**布局管理器LayoutManager才真正负责RecyclerView的填充、回收、测量、布局、滚动的逻辑。在源码中,RecyclerView的measure测量方法和layout布局方法实际上调用了LayoutManager的测量和布局方法。LayoutManager接管了RecyclerView的测量和布局流程。
**Recycler:**Recycler管理ViewHolder,负责ViewHolder的回收和复用,并实现4级缓存。4级缓存分别是屏幕内缓存、屏幕外缓存、自定义缓存、缓存池缓存。 LayoutManager向Recycler要ViewHolder的时候,会依次从 屏幕内->屏幕外->自定义->缓存池获取。 获取不到就会走onCreateViewHolder方法来创建ViewHolder。
屏幕内缓存和屏幕外缓存通过position来获取,有就直接拿出来用,不会走onBindViewHolder方法来重新绑定数据,刷新展示。 屏幕外缓存的默认个数是2个。满了之后就会把原先添加进来的缓存到缓存池缓存中,再缓存后添加进来到自身。
自定义缓存是需要开发者自己来实现的一个扩展缓存。 用于其他缓存不能够满足需求的时候使用。 比如通过position来获取, 但是需要刷新数据的复用情况。
缓存池是通过ViewType来获取和存储ViewHolder的,这个VeiwType是int值。通过SparseArray进行键值对存储。默认一个VeiwType类型存储5个。超出之后就会把之前添加的移除后再添加。
**ViewHolder内容:**里面存储的字段:itemView、mPosition、mItemViewType
**Adapter:**Adapter的作用就是将data数据,ItemView视图转换成ViewHolder。 对应上面说的onCreateViewHolder和onBindViewHolder方法。 并在数据刷新更新的时候刷新数据。
**Adapter的数据刷新:**通过观察者模式实现,在调用Adapter.notifyDataSetChanged中,AdapterDataObservable这个被观察者调用了每一个观察者AdapterDataObserver的onChange方法,在这个方法中,调用了RecyclerView的重新测量和布局,就回到了上面的LayoutManager接管RecyclerView的测量布局的流程上了。
# 优化
- 减少item的布局的嵌套层级、 减少ViewType的类型
- 根据业务需求配置缓存机制,如配置来回滑动的业务,配置屏幕外的缓存个数多些。如,内容不变,位置不变的view,自定义缓存机制实现create和bind只走一次
- 优化bind方法、 注意耗时操作、点击事件,业务数据计算(可在model中定义一个字段来避免重复计算)。因为bind方法在ViewHolder复用,刷新数据时会被再次调用
- item高度如果是固定的,可以设置给recyclerview.setHasFixedSize,优化测量性能
- 注意使用notifyDataSetChanged、局部修改可用notifyItemChanged这样的方法。
# 和ListView的不同
缓存机制的不同
ListView只有两级缓存,屏幕内缓存和屏幕外缓存。 RecyclerView有4级缓存,可参考上面。
复用对象的不同
ListView回收复用的View、RecyclerView回收复用的ViewHolder。因为ListView缓存的对象是View,在每次复用View时,都要通过View的findViewById来获取控件,非常消耗性能。
整体实现结构上的不同
ListView的测量、布局、滚动、缓存等等这些逻辑都是在ListView这一个类里面处理。 RecyclerView把这些逻辑抽离出来,托管给布局管理器、缓存管理器处理。这样扩展性更强,方便实现不同方式的列表布局和缓存。
一些API接口上的不同
ListView可以直接设置列表的headerView,footerView和emptyView。RecyclerView没有对应的方法。在滚动监听上二者所提供方法也不同。
# 自定义布局管理器
自定义布局管理是很强大也经常用到的功能。 当系统自带的线性布局、表格布局满足不了需求的时候,就需要自定义布局管理器了。 甚至很多可以用其他方式实现的功能,也可以通过自定义布局管理器来完成。比如,左右滑动的画廊效果,上下自动滚动的跑马灯效果,流式文本展示效果、抖音上下翻动效果,探探左右滑动选择卡片效果。
# 自定义布局管理的步骤
集成RecyclerView.LayoutManager,重写generateDefaultLayoutParams方法,这个方法返回的参数表示子view的布局方式,是warp还是match。 一般返回warp即可。
重写onLayoutChildren来实现第一次加载时,填充子view的逻辑。
重写canScrollVertically返回true来支持垂直滚动,重写scrollVerticallyBy来实现,view随手势上下滑的逻辑和滚动时子view的回收和填充逻辑。当然对应水平滑动的方法是,canScrollHorizontally和scrollHorizontallyBy。
自定义的管理器的难点主要在于view的填充和回收逻辑。
# 开发遇到的问题
进来之后自动滚动, recyclerview抢占焦点导致, 在布局添加:
android:focusable="true"
android:focusableInTouchMode="true"
# 添加间距
binding.rv.addItemDecoration(getItemDecoration());
private RecyclerView.ItemDecoration getItemDecoration() {
return new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
// if (parent.getChildAdapterPosition(view) == 0){ //给第一位的item设置50上边距
// outRect.top = 50;
// return;
// }
if (parent.getChildAdapterPosition(view) == state.getItemCount() -1 ){ //给最后一位的item设置50下边距
outRect.bottom = 80;
return;
}
}
};
}
# grid布局间距
int spanCount = 3; // 3 columns
int spacing = 10; // 50px
boolean includeEdge = false;
binding.rv.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount; //列数
private int spacing; //间隔
private boolean includeEdge; //是否包含边缘
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//这里是关键,需要根据你有几列来判断
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing; // item top
}
}
}
}
# 表格布局最后一行设置间距
android:paddingBottom="10dp"
android:clipToPadding="false"
# 设置分割线
int color = ContextCompat.getColor(this, R.color.bg_f2);
binding.rv.addItemDecoration(CommItemDecoration.createVertical(this, color, 1));
# 关闭拉伸效果
android:overScrollMode="never"
# RecyclerView做聊天页面
# 弹出键盘时,recyclerview内容被遮挡
https://www.cnblogs.com/liyiran/p/7490740.html
# 初始化adapter
public void initAdapter(WishAdapter.ItemLongClick itemLongClick) {
mAdapter = new WishAdapter();
mAdapter.setItemLongClick(itemLongClick);
mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
WishDetailActivity.start(ActivityManager.getInstance().getActivity(WishHomeActivity.class), mAdapter.getData().get(position).getMemberWorshipId());
}
});
binding.rv.setLayoutManager(new GridLayoutManager(this, 2));
binding.rv.setAdapter(viewModel.mAdapter);
}