RecyclerView底层工作原理解析及我的理解是否正确?
我用RecyclerView开发已经好几年了,但一直没彻底搞懂它的底层运行逻辑。最近研究了两篇讲解RecyclerView内部原理的文章后,才对它的工作机制有了更深的认识。
之前我对RecyclerView有个错误认知,举个例子:我做了个简单的列表项布局,XML里TextView默认文字颜色是白色,想在onBindViewHolder里只把第0项的文字改成黑色,其他项保持默认白色。
布局代码:
<?xml version="1.0" encoding="utf-8"?> <com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" />
Adapter代码:
@Override public void onBindViewHolder(@NonNull VH holder, int position) { if (position == 0) holder.row.txt.setTextColor(Color.BLACK); }
结果滚动列表时,发现有些非0位置的TextView也变成了黑色。网上说要在if后面加else把颜色改回白色,但我之前一直不理解——觉得if不执行的时候,应该会用XML默认的白色啊,怎么会变黑?
为了搞懂为啥必须加else,我研究了RecyclerView的底层逻辑,现在我用一个现实场景类比它的工作机制:
- 开餐馆买全新未改动的托盘,就像
onCreateViewHolder:RecyclerView从原始布局创建完全干净的新视图。 - 第一个顾客用这个托盘,离开时弄坏了一部分;后来第二个顾客用同一个托盘,哪怕点的餐不一样,还是会看到坏托盘。这就像
onBindViewHolder:TextView的内容是餐品,文字颜色是托盘被弄坏的部分——因为视图已经被修改过,复用的时候会带着之前的修改。 - 如果第三个顾客用这个托盘时又给弄坏了,这时候才会重新买新托盘?不对,应该是RecyclerView只会在需要新视图(比如屏幕显示不下,需要新的可见项)时才调用
onCreateViewHolder,其他时候都是复用已有的ViewHolder。
现在我想问:RecyclerView的工作机制是不是真的这样?我的理解有没有偏差?
你的理解核心是对的,RecyclerView的核心就是视图复用,你的类比也抓住了这个本质,但有个小偏差需要修正:
核心机制拆解
onCreateViewHolder的触发时机:它只会在RecyclerView需要新的ViewHolder来显示屏幕上的新条目时才会调用——比如刚打开页面,屏幕能显示10个条目,就会调用10次onCreateViewHolder创建10个ViewHolder。滚动时,滑出屏幕的ViewHolder会被放进回收池,滑入屏幕的新条目会直接复用回收池里的ViewHolder,不会再调用onCreateViewHolder(除非回收池里没有可用的ViewHolder,比如快速滚动时回收池跟不上)。onBindViewHolder的作用:每次复用ViewHolder时,都会调用这个方法给ViewHolder绑定当前条目的数据和样式。问题就出在这:你只给第0项设置了黑色,但复用的ViewHolder可能之前绑定过第0项,它的文字颜色已经是黑色了,如果你不给非0项重新设置回白色,它就会带着之前的黑色显示出来——这就是你看到非0项变黑的原因。
关于你的类比的修正
你说的“第三个顾客弄坏托盘才重新买新托盘”不对,RecyclerView不会因为ViewHolder被修改过就重新创建新的。不管ViewHolder之前被改成什么样,只要回收池里有可用的,就会拿来复用,然后调用onBindViewHolder让你给它设置当前条目的状态。所以你必须在onBindViewHolder里把所有可能变化的状态都覆盖设置——不管是满足条件还是不满足条件,都要明确设置,不能依赖XML的默认值,因为复用的ViewHolder已经不是“全新托盘”了。
正确的写法
@Override public void onBindViewHolder(@NonNull VH holder, int position) { if (position == 0) { holder.row.txt.setTextColor(Color.BLACK); } else { holder.row.txt.setTextColor(Color.WHITE); } }
或者更严谨的,用XML里的颜色资源:
holder.row.txt.setTextColor(ContextCompat.getColor(context, position == 0 ? R.color.black : R.color.white));
这样不管ViewHolder是新创建的还是复用的,都会被设置成当前条目应该有的颜色,就不会出现混乱了。
内容的提问来源于stack exchange,提问作者Taha Sami

