缓存
- AttachedScrap: 插入删除时,仅需将父View置null,保存在此
- cachedViews: 保存最近不显示在屏幕的ViewHolder,默认为2,相同位置才能复用
- mViewCacheExtension:自定义缓存,默认不实现
- RecyclerViewPool: 当cacheView满了后放入,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView(如果holder 的类型相同),默认为5
其他
- notifyDataSetChanged
会把所有设为无效,然后放入pool中,重新layout,多于五条剩下的要重新create,这样position顺序也会乱,所以要消耗很多资源
- payloads
是一个标志位,如果在一个item,只想刷新它的某个子控件,比如imageView?可以用到
- stableId
stableId设置为true后,即每一个itemView都有一个唯一的id来标识。当调用notifyDataSetChanged()方法时,ViewHolder会进入上面的一级缓存mAttachedScrap中,而不是进入缓存池pool中,这样的好处:1)不会存在缓存池pool满的问题,不需要重新createViewHolder; 2) 不需要重新bindView了。
卡顿如何解决
- recyclerView.setHasFixedSize(true) item大小固定时,提高性能,因为不需要测量子view了
- recyclerview会重用屏幕外的itemview,如果没有的话,要新建,可以提前缓存:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) { @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return 300; } };
- 可以把onclicklistener放在ViewHolder中,防止重新bind浪费时间,毕竟bind比create使用要频繁一点
- 局部刷新
notifyItemChanged(int position) notifyItemInserted(int position) notifyItemRemoved(int position) notifyItemMoved(int fromPosition, int toPosition) notifyItemRangeChanged(int positionStart, int itemCount) notifyItemRangeInserted(int positionStart, int itemCount) notifyItemRangeRemoved(int positionStart, int itemCount)
- 重写onScroll事件
对于大量图片的RecyclerView,滑动暂停后再加载;RecyclerView中存在几种绘制复杂,占用内存高的楼层类型,但是用户只是快速滑动到底部,并没有必要绘制计算这几种复杂类型
预加载
屏幕刷新绘制流程
UI Thread (input,animation,measure,layout,draw,sync):RenderNode ->
Render Thread ->
Open GL指令 -- GPU-->
Graphic Buffer ->
SurfaceFlinger(合并) ->
HAL层(绘制到屏幕)
- UI Thread :front buffer,back buffer Vsync通知两个缓冲区交换
- Render Thread:在Android L引入,只有开启硬件加速才会有,负责一部分渲染,减少UI Thread负担
- ChoreoGraphyer:Vsync 帧与Vsync联动,通过同步屏障机制,CPU、GPU腾出手来,从Vsync信号一到来就开始加载下一帧
大体流程就是:
- UI Thread通过measure,layout,draw等一系列流程把UI信息包装成RenderNode,然后交给Render Thread。
- Render Thread进而转化为open GL指令交给CPU GPU渲染。
- 渲染之后放在缓冲区内,然后交换缓冲区,屏幕对画面进行展示,并且等待下一次Vsync信号的到来。
- 而UI Thread要进行很多事,不止展示画面。ChoreoGraphyer的任务就是会让Vsync信号一到来就开始加载下一帧,毕竟画面流畅才是最能提升用户体验的。
机制
在两次Vsync信号中的空闲时间,GapWorker根据平均时间尝试create、bind ViewHolder:
如果create成功:放入cacheViews;
再进行bind,成功:放入RecycledViewPool。
对于嵌套 RecyclerView,要获取最佳的性能,在内部的 LayoutManager 中调用 LinearLayoutManager.setInitialItemPrefetchCount()方法。
例如:如果竖直方向的list至少展示三个条目,调用 setInitialItemPrefetchCount(4)
源码???
自定义layoutManager
layoutManager是一个抽象类,首先需要重写抽象方法,一般这么写:
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams { return RecyclerView.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) }
然后需要重写onLayoutChildren()
override fun onLayoutChildren( recycler: RecyclerView.Recycler?, state: RecyclerView.State? ) { // 一般需要要经过这几步 // val view = recycler.getViewForPosition(XXX) addView(view) // 测量view measureChildWithMargins(view, 0, 0) // view的布局,可以通过: // getDecoratedMeasuredWidth(view) 获取 Item View 的宽度 // getWidth() 获取RecyclerView宽度 // 来组合,确定位置 // 这里展示在RecyclerView中心位置,一般这里RecyclerView就可以正常显示 // 我在这里当时一直没有显示,换成官方layoutManager就可以。最后发现是RecyclerView // 的宽设置为了wrap_content int widthSpace = getWidth() - getDecoratedMeasuredWidth(view); int heightSpace = getHeight() - getDecoratedMeasuredHeight(view); layoutDecoratedWithMargins( view, widthSpace / 2, heightSpace / 2, widthSpace / 2 + getDecoratedMeasuredWidth(view), heightSpace / 2 + getDecoratedMeasuredHeight(view) ); }
左右滑动效果:
val itemTouchHelper = ItemTouchHelper(object :ItemTouchHelper.Callback(){ lateinit var mRecyclerView:RecyclerView override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int { // makeMovementFlags(int dragFlags, int swipeFlags) // dragFlags: 设置为可以向上和向下拖动 // swipeFlags: 设置成可以从左向右滑动删除 mRecyclerView = recyclerView return makeMovementFlags(0 , ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT ) } // 换位回调 override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return false } // 滑动回调 override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { (mRecyclerView.adapter as TantanActivity.FooViewDelegate).mData?.removeAt(viewHolder.layoutPosition) mRecyclerView.adapter?.notifyDataSetChanged() } }) itemTouchHelper.attachToRecyclerView(rv)
MultiType
MultiType是一个多类型列表视图的中间分发框架,它能帮助你快速并且清晰地开发一些复杂的列表页面,数据驱动视图。它的分内之事是做类型、事件与 View 的分发、连接工作。
多样化布局
- 可以采用
RecyclerView嵌套
RecyclerView的方式,比如在
vertical嵌套
horizontal的
RecyclerView
- 可以采用改写
spanSizeLookup 的方式,通过
itemType的不同种类把网格布局改为不同大小,从而实现应用商店类似效果
class RvActivity : AppCompatActivity() { lateinit var rv:RecyclerView private val adapter = MultiTypeAdapter() private val items = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_rv) rv = findViewById(R.id.test_rv) val layoutManager = GridLayoutManager(this, 5) layoutManager.spanSizeLookup = object :GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { return if (items[position] is Foo) 1 else 5 } } rv.layoutManager = layoutManager adapter.register(FooViewDelegate()) adapter.register(OooViewDelegate()) for (i in 0..19) { items.add(Foo(i.toString())) } for (i in 0..19) { items.add(Ooo(i.toString())) } adapter.items = items rv.adapter = adapter adapter.notifyDataSetChanged() } class FooViewDelegate: ItemViewDelegate<Foo, FooViewDelegate.ViewHolder>() { override fun onCreateViewHolder(context: Context, parent: ViewGroup): ViewHolder { return ViewHolder(LayoutInflater.from(context).inflate(R.layout.video_folder_item,parent,false)) } override fun onBindViewHolder(holder: ViewHolder, item: Foo) { holder.fooView.text = item.value } class ViewHolder(itemView : View): RecyclerView.ViewHolder(itemView) { val fooView: TextView = itemView.findViewById(R.id.video_folder_item_video_name) } } data class Foo( val value: String ) class OooViewDelegate: ItemViewDelegate<Ooo, OooViewDelegate.ViewHolder>() { override fun onCreateViewHolder(context: Context, parent: ViewGroup): ViewHolder { return ViewHolder(LayoutInflater.from(context).inflate(R.layout.video_folder_item,parent,false)) } override fun onBindViewHolder(holder: ViewHolder, item: Ooo) { holder.fooView.text = item.value } class ViewHolder(itemView : View): RecyclerView.ViewHolder(itemView) { val fooView: TextView = itemView.findViewById(R.id.video_folder_item_video_name) } } data class Ooo( val value: String ) }
DiffUtil
注意事项
使用NestedScrollView来嵌套RecyclerView时,由于要测量RecyclerView高度,一开始所有的item都会create,bind造成卡顿。即缓存机制失效。
如果数据量较大,可以使用给RecyclerView添加header的方式
参考:
- 缓存:
- MultiType
- 自定义layoutManager
Building a RecyclerView LayoutManager – Part 1
自定义 LayoutManager,让 RecyclerView 效果起飞 - 掘金
掌握自定义 LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用 API。_World_Data的博客-CSDN博客