1 RecyclerView的ViewHolder
RecyclerView是我们在开发中经常会用到的控件,也是面试中经常会问到。RecyclerView相对ListView在用法上,最明显的一点就是它会强制我们使用ViewHolder。那么,ViewHolder到底是什么呢,它和我们常说的复用有没有关系呢。
其实,ViewHolder和复用是没有关系的。在ListView中,我们是把findViewById()的过程放在ViewHolder中,然后再把ViewHolder存在convertView的Tag。然后通过getTag()获取存储的ViewHolder。而这个我们存储的ViewHolder是已经findViewById()的。所以不需要重新findViewById()。
所以,我们知道了ViewHolder是用来解决重复绑定id这个过程,因为findViewById()是一个比较耗费资源的过程。
2 RecyclerView的缓存机制
我们常说RecyclerView有着比ListView更好的性能。其中,一个重要的原因就是因为RecyclerView有着比ListView更好的缓存机制。想对RecyclerView的缓存有更深刻的了解,我们先了解一下ListView的缓存。
ListView共有两级缓存,它是通过RecycleBin来管理,先是从Activie View获取,如果Activie View获取不到,就从Scrap View获取。Activie View就是在屏幕中显示的Item,Scrap View就是在屏幕外的Item。为什么屏幕显示的Item也需要缓存呢,因为android的屏幕是不断刷新的,它每次刷新的时候把最新的View渲染到屏幕上,它就不需要重新绑定数据,通过Activie View就可以实现。Scrap View是负责重新绑定数据的,移出屏幕的Item会被放入Scrap View中。如果Scrap View中也找不到,就由我们在getView()中创建新的Item。
RecyclerView相比ListView,缓存机制会更为复杂。RecyclerView总共有四层缓存,是通过Recycler来管理。第一层是Scrap,第二层是Cache,第三层是ViewCacheExtension,第四层是RecycledViewPool。
Scrap是RecyclerView的一级缓存,但是它又和ListView的二级缓存名字一样。它在屏幕里面的,是可以直接复用的,它是通过position来找到对应的View的。刚出屏幕的几个Item,它会放在第二层的cache里面。它和第一层的Scrap是差不多,它是可以直接复用的,不需要重新绑定,而且它也是用position来找的。它和Scrap不同的是,它是已经出了屏幕的.
第三层叫ViewCacheExtension,是用户自己定义的Cache策略。这个在实际中用的比较少。最后一个是RecycledViewPool,它里面的数据需要重新绑定的,它里面是通过viewType来找到数据。它不走onCreatViewHolder,但是会走onBindViewHolder。
3 RecyclerView的性能优化策略
1 onCreateViewHolder中设置点击监听,而不是在onBindViewHolder中设置,因为onBindViewHolder在一个item中是有可能调用多次的,在里面设置点击事件有可能导致重复创建对象。
2 RecyclerView.setHasFixedSize(true),如果 Adapter 的数据变化不会导致 RecyclerView的大小变化的时候,我们应该调用这个方法。它可以避免RecyclerView重新计算大小。
3 当多个 RecyclerView的Item是一样时,可以共用RecycledViewPool。
RecyclerView.RecycledViewPool recycledViewPool =new RecyclerView.RecycledViewPool();
recyclerView1.setRecycledViewPool(recycledViewPool);
recyclerView2.setRecycledViewPool(recycledViewPool);
4 RecyclerView的分割线
ListView设置分割线是比较简单的,可以直接在xml中设置它的颜色和粗细。RecyclerView相比来说,设置分给先是比较复杂的。简单的分割线可以用下面的方式设置。
RecyclerView recyclerView = new RecyclerView(this); recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
为什么调用上述代码就可以设置分割线呢,我们来看一下DividerItemDecoration的源码。
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); } } }
我们可以看到在DividerItemDecoration中有onDraw()这个方法,但是它本身并不会调用,而是会由RecyclerView来调用,具体调用如下。
public void onDraw(Canvas c) { super.onDraw(c); int count = this.mItemDecorations.size(); for(int i = 0; i < count; ++i) { ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState); } }
DividerItemDecoration的onDraw()方法中,会调用传入的参数调用drawVertical()或drawHorizontal(),我们选择其中一个,看一下drawVertical的源码。
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(); }
可以看到,这是个和绘制相关的方法,中间有一个遍历的过程。其实,这个就是绘制那条分割线。后面还有一个关键的方法,叫getItemOffsets()。offset都是和偏移有关的,所以这段代码是设置偏移的。当然,添加分割线整体代码还是非常复杂的,还要和RecyclerView的源码联系起来。这里只分析几个重要的方法,其它就不一一细说了
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); } } }
RecyclerView的分割线为什么要做的那么复杂呢,为什么不做的像ListView那么简单呢。添加分割线的这个方法名叫addItemDecoration(),翻译成中文就是添加一个装饰。其实它的目的不只是做分割线这么简单,一些对Item的修改、装饰等都可以通过这个方法来实现。举个例子,比如你想让某些图片的背景变得昏暗一些,可以在上面绘制多一层。或者你想自己自己定义分割线的样式,分割线不是百分百长度,而是百分之七八十长度的,也可以自己设置。这些都可以通过addItemDecoration()这个方法来实现
5 总结
至此,RecyclerView的一些较难的技术已经分析完了。RecyclerView虽然容易上手,性能也比ListView更好,但是它的很多原理,很多人应该还不是完全理解,特别是和ListView的缓存对比这一块,经常在面试中会被问到,希望这篇文章可以帮助到大家。