recyclerview

134 阅读5分钟

缓存

  • 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了。

卡顿如何解决

  1. recyclerView.setHasFixedSize(true) item大小固定时,提高性能,因为不需要测量子view了
  2. recyclerview会重用屏幕外的itemview,如果没有的话,要新建,可以提前缓存:

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) { @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return 300; } };

  1. 可以把onclicklistener放在ViewHolder中,防止重新bind浪费时间,毕竟bind比create使用要频繁一点
  2. 局部刷新

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)

  1. 重写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信号一到来就开始加载下一帧

大体流程就是:

  1. UI Thread通过measure,layout,draw等一系列流程把UI信息包装成RenderNode,然后交给Render Thread。
  2. Render Thread进而转化为open GL指令交给CPU GPU渲染。
  3. 渲染之后放在缓冲区内,然后交换缓冲区,屏幕对画面进行展示,并且等待下一次Vsync信号的到来。
  4. 而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 的分发、连接工作。

多样化布局

  1. 可以采用

RecyclerView嵌套

RecyclerView的方式,比如在

vertical嵌套

horizontal的

RecyclerView

  1. 可以采用改写

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的方式

参考:

  • 缓存:

再也不用担心问RecycleView了

  • MultiType

Android 复杂的列表视图新写法 MultiType

  • 自定义layoutManager

Building a RecyclerView LayoutManager – Part 1

把RecyclerView撸成 马 蜂 窝

自定义 LayoutManager,让 RecyclerView 效果起飞 - 掘金

玩转仿探探卡片式滑动效果 - 掘金

掌握自定义 LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用 API。_World_Data的博客-CSDN博客