在面试中经常会遇见面试RecycleView的,RecycleView源代码及其复杂,想要回答好真的很困难,下面我就说一下我的理解。
先说RecycleView的用法。
1、设置一个LayoutManager 用于设置布局相关。
2、设置Adapter 用于将布局和数据进行绑定。
3、可以设置显示动画(这个是非必选)。
4、可以设置RecycleView的间隔(这个也是非必选)。
RecycleView说到底也只是一个自定义ViewGroup。所以先看一下onMeasure()是如何测量的。
onMeasure
首先会判断是否传入LayoutManager,如果没有则直接测量自身大小,如果自身大小确定,则显示自身大小否则不会显示大小因为它没有测量子view。
如果大小确定直接调用mLayoutManager的onMeasure方法去测量RecycleView的高,否则dispatchLayoutStep2方法进行测量并且布局。
onLayout
在这里会调用dispathLayout方法,这个里面仍然会dispathLayoutStep2这个方法。
在dispathLayoutStep2会接着调用 mLayout.onLayoutChildren()方法,不同的LayoutManager有不同的onLayoutChildren方法,这里分析LinearLayoutManager的onLayoutChildren()方法。
onLayoutChildren方法会调用fill方法对子view布局测量,在fill里面会先调用layoutChunk方法对子view进行布局和测量,然后fill会计算剩余的空间。layoutChunk会从缓存里获取到一个view,然后测量这个itemview的大小,并且确定itemview位置。
onDraw
会调用itemDecoration的onDraw方法画item间隔。
关于RecycleView的缓存
RecycleView之所以这么厉害,Recycler了。
RV主要实现了ViewHolder的缓存以及复用。核心代码在Recycler中完成。
里面主要分为四个部分。
第一个部分是scrap部分,当调用notifyXXX相关的代码的时候会将当前屏幕的ViewHolder缓存到scrap List数组中。
第二部分cacheViews数组,默认是2个,如果数组满了,按照FIFO的规则移除ViewHolder,通常情况下刚移除屏幕的viewholder并不会马上删除。而是存在cacheViews数组中。
第三部分ViewCacheExtsension 自定义的缓存策略一般用的比较少,也不建议用。
第四部分RecycleViewPool 也是缓存屏幕外的ViewHolder,当CachedView数据已满,则会将CachedView抛弃的ViewHolder交给RecycleViewPool,但是ViewHolder的数据会被删除,再次调用的话需要调用onBinderViewHolder绑定数据,这和ListView使用ViewHolder复用convertView道理一致。
取出缓存
从Recycler里面按照顺序先获取Scrap里面的缓存,之后是cacheview里面的 在之后是cacheViewExtension里面的,最后是从RecycleViewPool里获取数据。如果都获取不到,则会调用Adapter的createViewHolder()方法生成一个ViewHolder。
当然也可以自定义ItemView的动画,以及添加ItemDecortion每一个item的间隔。
rv.addItemDecoration(new RecyclerView.ItemDecoration() {
private int mDividerHeight = 10;
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
for ( int i = 0; i < childCount; i++ ) {
View view = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
//第一个ItemView不需要绘制
if ( index == 0 ) {
continue;
}
float dividerTop = view.getTop() - mDividerHeight;
float dividerLeft = parent.getPaddingLeft();
float dividerBottom = view.getTop();
float dividerRight = parent.getWidth() - parent.getPaddingRight();
c.drawRect(dividerLeft,dividerTop,dividerRight,dividerBottom,mPaint);
}
}
/**
*
* @param outRect
* @param view
* @param parent
* @param state
*
* 可以通过 outRect.set(l,t,r,b) 来指定 itemview的paddingLeft,paddingTop, paddingRight, paddingBottom
*/
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
// TODO Auto-generated method stub
super.getItemOffsets(outRect, view, parent, state);
//第一个ItemView不需要在上面绘制分割线
if (parent.getChildAdapterPosition(view) != 0){
//这里直接硬编码为1px
outRect.top = mDividerHeight;
}
}
});
//添加动画,我们可以模拟DefaultItemAnimator 定义自己的动画
rv.setItemAnimator(new DefaultItemAnimator());
findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
list.add(2,"add");
rvAdapter.notifyItemInserted(2);
}
});
findViewById(R.id.btn_delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
list.remove(2);
rvAdapter.notifyItemRemoved(2);
}
});