RecyclerView基础使用
备注:推荐两个开源库
本系列博客代码位置: gitee.com/jixiaoxin-5…
RecyclerView作为Android最常用控件之一,在使用上其实还是蛮复杂的,能用是一回事,但要用的好却不容易了
一 RecyclerView能做什么
各种各样的数据列表,聊天列表,使用RecyclerView都可以实现
基础使用,必要条件
必要条件就两个,Adapter 和 LayoutManager
Adapter: 主要负责将数据转换成view上的内容
LayoutManager: 我们都知道,ViewGroup有onLayout()方法,用于决定子View的排列位置, RecyclerView是一个ViewGroup,所以LayoutManager 就是用来Manager(管理)子View的Layout(排列方式)的
其他的内容还有很多:
ItemDecoration 负责添加分割线
ItemAnimator 设置Item增加、移除动画
Item 的拖拽
Tree 树列表 多级列表 收起与展开
DiffUtil 差异化更新
快速滑动,重item优化 滑动完毕再加载网络资源
这些都在后面的系列博客再说
二 Adapter 基础方法
1 各个方法的用途
Adapter需要继承 RecyclerView.Adapter 同时有一个 RecyclerView.ViewHolder 的泛型
ViewHolder 的职责是创建并持有view视图,可以把它理解成一个个RecyclerView里的子View,RecyclerView的缓存复用其实就是复用一个个的ViewHolder
然后就是Adapter的各个基础方法的用处
class Demo0Adapter(private val mContext: Context): RecyclerView.Adapter<Demo0Adapter.ItemViewHolder>() {
private var items = mutableListOf<String>() //数据源
fun clearList(){
items.clear()
notifyDataSetChanged() //刷新所有列表
}
fun addList(list: MutableList<String>){
items.addAll(list)
notifyItemRangeChanged(items.size - list.size - 1,list.size) //刷新对应下标范围的数据 第一个参数的开始刷新的位置,第二个刷新item的个数
}
fun setNewList(list: MutableList<String>){
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
fun getItems(): MutableList<String>{
return items
}
private lateinit var demo0AdapterListener: Demo0AdapterListener
fun setDemo0AdapterListener(mainAdapterListener: Demo0AdapterListener){
this.demo0AdapterListener = mainAdapterListener
}
inner class ItemViewHolder(view: View): RecyclerView.ViewHolder(view){
val textView: TextView = view.findViewById(R.id.textView)
val rootView: ConstraintLayout = view.findViewById(R.id.rootView)
}
/**
* 用于onCreateViewHolder时创建不同的 ViewHolder
* */
override fun getItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
/**
* @param parent 这里应该是RecyclerView
* @param viewType getItemViewType(position: Int)传过来的
* 创建 ViewHolder
* */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.item_demo0, parent,false)
val itemViewHolder = ItemViewHolder(view)
itemViewHolder.rootView.setOnClickListener {
if(::demo0AdapterListener.isInitialized){
demo0AdapterListener.clickItems(itemViewHolder.adapterPosition)
}
}
return itemViewHolder
}
/**
* 用于差异化更新
* 使用payloads进行数据对比,再酌情进行数据的刷新
* */
override fun onBindViewHolder(
holder: ItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
super.onBindViewHolder(holder, position, payloads)
}
/**
* 对ViewHolder进行数据绑定
* */
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.textView.text = items[position]
}
/**
* 决定 RecyclerView 的子View个数
* */
override fun getItemCount(): Int {
return items.size
}
}
我会比较推荐数据源让Adapter持有,这样操作数据的时候会记得调用对应的刷新方法,事实上,在我待过的所有公司他们都并不这么做,很容易出现数据改变了,但是RecyclerView显示的列表没跟着改变,这个时候如果去操作item,很容易出现 NullPointerException 操作空对象 引用 和 数组越界等问题
同时,如果setNewList方法是如下这么写的话
fun setNewList(list: MutableList<String>){
items = list
notifyDataSetChanged()
}
其实指向的是外部的引用,如果外部的数据之后进行其他操作,也容易出现上述问题
2 创建ViewHolder
getItemViewType 获取设置的 ViewType之后才会 执行 onCreateViewHolder 创建 VieHolder , 多Type的界面就是根据不同ViewType创建的不同的ViewHolder
3 刷新视图
notify--- 开头的这些刷新方法一般在数据改变后调用,adaper会执行相应的onBindViewHolder,要注意的就是千万不要一概的调用 notifyDataSetChanged 他会直接刷新所有,如果此时列表有几百几千条数据,很容易造成卡顿。 然后就是DiffUtil差异化更新,还有刷新相关的动画,这个之后会专门写一篇
4 设置点击事件
点击事件的设置不要在onBindViewHolder里设置,只需要在视图创建(onCreateViewHolder)的时候设置一次就行,而onBindViewHolder很可能会多次调用
itemViewHolder.layoutPosition
itemViewHolder.adapterPosition
- getLayoutPosition 和 getAdapterPosition 通常情况下是一样的,只有当 Adapter 里面的内容改变了,而 Layout 还没来得及绘制的这段时间之内才有可能不一样,这个时间小于16ms
- 如果调用的是 notifyDataSetChanged(),因为要重新绘制所有 Item,所以在绘制完成之前 RecyclerView 是不知道 adapterPosition 的,这时会返回-1(NO_POSITION)
- 但如果用的是 notifyItemInserted(0),那立即就能获取到正确的 adapterPosition,即使新的 Layout 还没绘制完成,比如之前是0的现在就会变成1,因为插入了0, 相当于 RecyclerView 提前帮你计算的,此时getLayoutPosition 还只能获取到旧的值。
- 总的来说,大多数情况下用 getAdapterPosition,只要不用 notifyDataSetChanged() 来刷新数据就总能立即获取到正确 position 值。
5 与DataBinding的使用
有的item布局较为复杂,随着item内控件与item类型的增多,每次findViewById其实也很麻烦,而如果使用DataBinding,就会方便很多 (ViewBinding也差不多)
class Demo0DataBindingAdapter(private val mContext: Context): RecyclerView.Adapter<Demo0DataBindingAdapter.ItemViewHolder>() {
private var items = mutableListOf<String>() //数据源
fun clearList(){
items.clear()
notifyDataSetChanged() //刷新所有列表
}
fun addList(list: MutableList<String>){
items.addAll(list)
notifyItemRangeChanged(items.size - list.size - 1,list.size) //刷新对应下标范围的数据 第一个参数的开始刷新的位置,第二个刷新item的个数
}
fun setNewList(list: MutableList<String>){
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
fun getItems(): MutableList<String>{
return items
}
private lateinit var demo0AdapterListener: Demo0AdapterListener
fun setDemo0AdapterListener(mainAdapterListener: Demo0AdapterListener){
this.demo0AdapterListener = mainAdapterListener
}
inner class ItemViewHolder(val binding: ItemDataBindingBinding): RecyclerView.ViewHolder(binding.root)
/**
* 用于onCreateViewHolder时创建不同的 ViewHolder
* */
override fun getItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
/**
* @param parent 这里应该是RecyclerView
* @param viewType getItemViewType(position: Int)传过来的
* 创建 ViewHolder
* */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val itemViewHolder = ItemViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(mContext),
R.layout.item_data_binding,
parent,
false
)
)
with(itemViewHolder.binding){
rootView.setOnClickListener {
if(::demo0AdapterListener.isInitialized){
demo0AdapterListener.clickItems(itemViewHolder.adapterPosition)
}
}
}
return itemViewHolder
}
/**
* 用于差异化更新
* 使用payloads进行数据对比,再酌情进行数据的刷新
* */
override fun onBindViewHolder(
holder: ItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
super.onBindViewHolder(holder, position, payloads)
}
/**
* 对ViewHolder进行数据绑定
* */
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
//holder.textView.text = items[position]
holder.binding.textView.text = items[position]
}
/**
* 决定 RecyclerView 的子View个数
* */
override fun getItemCount(): Int {
return items.size
}
}
不仅不用写 findViewById,也避免引用到别的布局的id导致异常,同时dataBinding还有数据绑定的功能 ,上面只是最浅显的应用,databinding的数据绑定更大的好处应该是,我们只需要改变集合中的某些数据,对应列表的值也会相应改变,如果删除或添加某个item,列表也做相应动画,这才叫数据绑定,不过这些功能实现起来比较麻烦,这里就先不说了,之后有时间再专门出一篇RecyclerVIew的数据绑定文章