- BaseAdapter封装(一) 简单封装
- BaseAdapter封装(二) Header,footer
- BaseAdapter封装(三) 空数据占位图
- BaseAdapter封装(四) PageHelper
- BaseAdapter封装(五) ListAdapter
- ListAdapter封装, 告别Adapter代码 (上)
- ListAdapter封装, 告别Adapter代码 (中)
- ListAdapter封装, 告别Adapter代码 (下)
- BaseAdapter封装(八) Paging 分页
ListAdapter封装 (中) - 多条目, 头尾, 嵌套, 单多选.
前言:
上一篇文章已经讲解 SimpleAdapter 的基本封装. 这次我们将用 ConcatAdapter 封装头尾, 并封装多类型Adapter, 嵌套RecycleView, 单多选Adapter;
目录:
- 用 ConcatAdapter 封装头尾;
- 用泛型, 改建 BaseAdapter
- 多条目 MultipleAdapter
- 嵌套 NestedAdapter
- 单选 SingleChoice
- 多选 MultipleChoice
1.用 ConcatAdapter 封装头尾;
题外话: 博主之前学 ListAdapter 的时候, 为封装头尾可费了老劲 [撇嘴] ;
一开始用多条目类型, 汇总条目数的方式; 然后 头是出来了,但是首次刷列表会自动滚到底??? 博主四眼懵逼. 也没找到原因;
然后 博主改用假实体的方式, 单独设置头尾对象, 重写 submitList 带上头尾实体计算. 最终Ok, 想的是MVVM对 position不是太敏感了. 然后出现了 ConcatAdapter [鼓掌]
1.1 ConcatAdapter 顺序的连接其他 Adapter:
ConcatAdapter 是 recyclerview: 1.2.0-alpha 04 中提供的一个新组件;
它可以帮我们顺序地组合多个 Adapter,并让它们显示在同一个 RecyclerView 中。
想了解小伙伴 点这里
1.2 思路:
以前我们头尾是单独 ViewType; 现在呢,它们变成了单独的 Adapter; 所以, 现在要创建 单条目的Adapter
EndAdapter: 只有一个条目, 也不需要绑定数据, View由Activity或Fragment控制;
EndHolder: 继承 ViewHolder, 传递View即可
/** * 头尾 Adapter; View由外部 维护; */
class EndAdapter(val view: View) : RecyclerView.Adapter<EndHolder>(){ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = EndHolder(view) override fun onBindViewHolder(holder: EndHolder, position: Int) {} override fun getItemCount() = 1 } class EndHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
1.3 在我们的 BaseAdapter加入:
withHeaderAndFooter() 传入头尾View 就OK了;
/**
* 组装头尾, 返回一个新的 ConcatAdapter 实体, 需要将新实体设置给 RecycleView
*/
fun withHeaderAndFooter(
header: View? = null,
footer: View? = null
) = when{
header != null && footer != null -> ConcatAdapter(EndAdapter(header), this, EndAdapter(footer))
header != null && footer == null -> ConcatAdapter(EndAdapter(header), this)
header == null && footer != null -> ConcatAdapter(this, EndAdapter(footer))
else -> this
}
1.4 使用:
只需要将 withHeaderAndFooter() 返回的 Adapter 赋给 RecycleView即可, 我们还是操作 主Adapter
val headBinding = ViewHeaderTjBinding.inflate(LayoutInflater.from(mActivity), mView as @Nullable ViewGroup, false) //主数据 Adapter mAdapter = MultipleAdapter() //将这个由 withHeaderAndFooter() 返回的 ConcatAdapter 赋给 RecycleView 即可 val mmAdapter = mAdapter.withHeaderAndFooter(headBinding.root) mDataBind.rvRecycle.let { it.layoutManager = LinearLayoutManager(mActivity) it.adapter = mmAdapter }
2. BaseAdapter 改造, 传入实体泛型;
2.1 首先 DiffCallback 必须要用泛型改造;
class DiffCallback<T : BaseItem>: DiffUtil.ItemCallback<T>() { /** * 比较两个条目对象 是否为同一个Item */ override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem === newItem } /** * 再确定为同一条目的情况下; 再去比较 item 的内容是否发生变化; * 我们使用 状态标识方式判断; * @return true: 代表无变化; false: 有变化; */ override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { return !oldItem.hasChanged } }
2.2 在 BaseAdapter 中加入方便本地操作的 增删改 方法; 最终代码如下
abstract class BaseAdapter<T: BaseItem>( protected val handler: BaseHandler? = null) : ListAdapter<T, NewViewHolder>(DiffCallback()) { override fun onBindViewHolder(holder: NewViewHolder, position: Int) { holder.bind(getItem(position)) } /** * 重写 提交数据方法, 让它必定以新数据集合对象传入 */ override fun submitList(list: MutableList<out T>?) { val newData = mutableListOf<T>() if(list != null){ newData.addAll(list) } super.submitList(newData) } /** * 删除指定条目 */ fun remove(entity: BaseItem){ removeAt(currentList.indexOf(entity)) } fun removeAt(position: Int){ if(position == -1) return if(position >= currentList.size) return val newData = mutableListOf<T>() newData.addAll(currentList) newData.removeAt(position) super.submitList(newData) } /** * 修改指定条目 */ fun update(entity: T){ updateAt(currentList.indexOf(entity)) } fun updateAt(position: Int){ if(position == -1) return if(position >= currentList.size) return notifyItemChanged(position) } /** * 新增条目 */ fun insert(entity: T, position: Int = -1){ val newData = mutableListOf<T>() newData.addAll(currentList) if(position < newData.size && position >= 0){ newData.add(position, entity) }else{ newData.add(entity) } super.submitList(newData) } /** * 组装头尾, 返回一个新的 ConcatAdapter 实体, 需要将新实体设置给 RecycleView */ fun withHeaderAndFooter( header: View? = null, footer: View? = null ) = when{ header != null && footer != null -> ConcatAdapter(EndAdapter(header), this, EndAdapter(footer)) header != null && footer == null -> ConcatAdapter(EndAdapter(header), this) header == null && footer != null -> ConcatAdapter(this, EndAdapter(footer)) else -> this } }
2.3 SimpleAdapter 只需要改成 继承 BaseAdapter<BaseItem> 即可;
open class SimpleAdapter( private val layout: Int, handler: BaseHandler? = null ) : BaseAdapter<BaseItem>(handler)
3. 多条目类型 MultipleAdapter
多类型其实是很简单的, 还是重写 getItemViewType();
有区别的是, getItemViewType() 的返回值, 直接返回 布局文件ID. 然后实体类自行判断给出 LayoutId
3.1 实体类:
还是实现 BaseItem, 重写 getMItemType() , 返回布局文件 ID
class MultipleEntity( var name: String, var index: Int = 0, override var hasChanged: Boolean = false) : BaseItem { /** * 实体类自行判断, 并给出 布局id */ override fun getMItemType(): Int { return if(index % 2 == 0){ R.layout.item_multiple_one }else{ R.layout.item_multiple_two } } }
3.2 MultipleAdapter:
需要重写 getItemViewType() 并将实体类的 getMItemType() 结果返回;
创建 ViewHolder 的时候, 直接用 viewType 作为布局ID (因为我们用布局ID做的ViewType)
/** * 多条目 类型 Adapter; * 1.实体类需要重写 {@link BaseItem} 的 getMItemType() 方法; 并根据类型,返回不同的 LayoutId * 2.复杂 Adapter. 还需要自定义; 继承 MultipleAdapter 并重写 getItemViewType() */ open class MultipleAdapter(handler: BaseHandler? = null) : BaseAdapter<BaseItem>(handler) { /** * Item类型, 同 layoutId; */ override fun getItemViewType(position: Int): Int { return currentList[position].getMItemType() } /** * viewType 同 layoutId */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder { if(viewType == 0) throw RuntimeException("Undefined itemViewType") return NewViewHolder( DataBindingUtil.inflate( LayoutInflater.from(parent.context), viewType, parent, false ), handler ) } }
3.3 接下来就是用了;
很简单是不是, 直接把数据集合塞进Adapter里 就完事了
mAdapter = MultipleAdapter()
mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
}
val data = mutableListOf<MultipleEntity>()
repeat(30){
if(it % 2 == 0){
data.add(MultipleEntity("小美", it))
}else{
data.add(MultipleEntity("小状", it))
}
}
mAdapter.submitList(data)
3.4 布局文件也帖出来吧
item_multiple_one.xml <layout> <data> <variable name="item" type="com.example.kotlinmvpframe.yiyou.entity.MultipleEntity" /> </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:padding="12dp"> <TextView style="@style/tv_base_16_dark" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{item.name + " 我是类型one,我有靓照"}' app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"/> <ImageView style="@style/img_wrap" android:layout_width="60dp" android:layout_height="50dp" android:src="@drawable/bg" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout> item_multiple_two.xml <layout> <data> <variable name="item" type="com.example.kotlinmvpframe.yiyou.entity.MultipleEntity" /> </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:padding="12dp"> <TextView style="@style/tv_base_16_dark" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{item.name + " 我是类型two,我有money"}' app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"/> <TextView style="@style/tv_base_16_dark" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="italic|bold" android:textColor="@color/shape_red" android:text="¥. 500" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
3.5 再贴上效果图 : [奸笑]
4. 嵌套 NestedAdapter
4.1 嵌套 RecycleView 的注意点:
嵌套子层数据集合: 一般在外层实体中; 例如 一条朋友圈中多张图片. 外层实体持有 Image集合; 我们就用接口的方式, 规范这个集合
子层RecycleView: 如果固定的 ViewDataBinding 对象, 我们可以直接拿它的 RecycleVIew. 但是 它不固定咋办? 也没法封装继承啊! 好吧, 好办 [机智]
子层的 Adapter: 这个好办, 上一篇我们已经封装好了 SimpleAdapter;
子层列表事件: 子层的事件, 还是用 handler 处理, 但最终由回调函数,调到主界面处理;
4.2 思路
首先外层列表的主数据绑定, 没有变化, 还是用总的 ViewHolder.bind() 方式;
其次我们规范 嵌套子层 RecycleView 的ID, 用 findViewById 的方式 把它存到 ViewHolder 里;
实体类实现 BaseNestedItem 接口的 getNestedList() 函数; 把子层集合返回;
4.3 上代码:
/** * 单条目, 简单Item MVVM Adapter * 复杂类型 还是得自定义 Adapter */ open class NestedAdapter<T: BaseNestedItem>( /** * 外层列表 item布局; */ private val layout: Int, /** * 内层列表 item布局; */ private val nestedLayoutId: Int, /** * 生成子层Recycle的 LayoutManager; 可以是 LinearLayoutManager GridLayoutManager 等; * 默认 横向 LinearLayoutManager */ private val childLayoutManager: (() -> RecyclerView.LayoutManager)? = null, /** * 子层列表点击回调; 此方式可能不太优雅. 有待改进 * arg1 子层点击 View * arg2 外层点击 position; 这个position并不保险; 当存在 Header 时, 它需要 -1 * arg3 子层实体对象 */ private val childClickListener: ((View, Int, BaseItem) -> Unit)? = null, /** * 外层类表点击事件; */ handler: BaseHandler? = null ) : BaseAdapter(handler) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder { val bingding: ViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), layout, parent, false) // 统一规范 子层 RecycleView 的id; 将 RecyclerView 存入 NestedHolder val rvNested = bingding.root.findViewById<RecyclerView>(R.id.rv_nested_item) return NestedHolder(rvNested, bingding, handler) } override fun onBindViewHolder(holder: NewViewHolder, position: Int) { if(holder is NestedHolder){ //绑定子层 Recycle Adapter createNestedAdapter(holder) //绑定子层 数据 (holder.rvNested?.adapter as SimpleAdapter?)?.submitList(getItem(position).getNestedList()) } super.onBindViewHolder(holder, position) } /** * 绑定子层 Recycle 的 Adapter */ private fun createNestedAdapter(holder: NestedHolder) { holder.rvNested?.let { if(it.adapter == null){ //子层点击事件; null 则不设置; val handler = if(childClickListener == null){ null }else{ object : Handler<BaseItem>(){ override fun onClick(view: View, info: BaseItem) { childClickListener.invoke(view, holder.layoutPosition, info) } } } val adapter = SimpleAdapter(nestedLayoutId, handler) it.layoutManager = childLayoutManager?.invoke() ?: LinearLayoutManager(it.context, RecyclerView.HORIZONTAL, false) it.adapter = adapter } } } /** * 重写 ViewHoleder, 附带 RecyclerView */ class NestedHolder(val rvNested: RecyclerView?, binding: ViewDataBinding, handler: BaseHandler?) : NewViewHolder(binding, handler) }
4.4 讲解:
可以看出, NestedAdapter 的内外层布局 ID直接传入; 内层 LayoutManager, 及事件响应 由kotlin高阶函数提供;
注意: 内层事件响应时, 回调给了外层position 是通过 holder.layoutPosition; 但是在有头部的Adapter中 layoutPosition 是需要 -1 的;
还有一个 holder.bindingAdapterPosition 参数, 但是它在 带头部的ConcatAdapter 中使用时, 经常返回 -1; 不知道是不是博主使用姿势不对;
4.5 BaseNestedItem: getNestedList() 函数统一规范子集合;
interface BaseNestedItem : BaseItem{ fun getNestedList(): MutableList<out BaseItem>? }
4.6 实体类:
class MultipleEntity( var name: String, override var hasChanged: Boolean = false) : BaseNestedItem { var imgs: MutableList<ImageEntity>? = null // 返回子层集合 override fun getNestedList(): MutableList<ImageEntity>? = imgs /** * 子层集合实体类 */ class ImageEntity(val res: Int, override var hasChanged: Boolean = false) : BaseItem }
4.7 使用:
mAdapter = NestedAdapter<MultipleEntity>( //主列表布局 ID R.layout.item_nested_one, //子列表布局 ID R.layout.item_img, //事件响应 childClickListener = { _, layoutPosition, info -> val childIndex = (mAdapter.currentList[layoutPosition] as MultipleEntity).imgs?.indexOf(info) ?: 0 Toast.makeText(mActivity.applicationContext, "点击了第${layoutPosition + 1} 的第${childIndex + 1}个条目", Toast.LENGTH_SHORT).show() }) mDataBind.rvRecycle.let { it.layoutManager = LinearLayoutManager(mActivity) it.adapter = mAdapter } val data = mutableListOf<MultipleEntity>() repeat(5){ val entity = if(it % 2 == 0){ MultipleEntity("小美", it) }else{ MultipleEntity("小状", it) } entity.imgs = mutableListOf( ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg), ImageEntity(R.drawable.bg)) data.add(entity) } mAdapter.submitList(data)
4.8 布局贴出来
item_nested_one.xml <layout> <data> <variable name="item" type="com.example.kotlinmvpframe.yiyou.entity.MultipleEntity" /> </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:padding="12dp"> <TextView android:id="@+id/tv_name_item" style="@style/tv_base_16_dark" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{item.name}' app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_nested_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" app:layout_constraintTop_toBottomOf="@id/tv_name_item"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout> item_img.xml <layout> <data> <variable name="item" type="com.example.kotlinmvpframe.yiyou.entity.MultipleEntity.ImageEntity" /> <variable name="handler" type="com.example.kotlinmvpframe.test.testtwo.Handler" /> </data> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{v -> handler.onClick(v, item)}" android:padding="8dp"> <ImageView style="@style/img_wrap" android:layout_width="80dp" android:layout_height="80dp" app:imgRes="@{item.res}"/> </FrameLayout> </layout>
4.9 效果图来了:
5. SingleChoiceAdapter 单选
5.1 分析:
- 实体需要有 checked 字段, 我们用 BaseCheckedItem 作为实体基类
- 规范 布局文件中 CheckBox 的 ID, onCreateViewHolder 时 通过 findViewById 将控件保存到 ViewHolder
- 选中某个条目时, 之前选中的条目需要反选. 所以必须要知道上一次选中的 实体和CheckBox
- 监听 onCheckedChanged 事件, 新实体选中, 就实体反选.
- 单选时, 不允许反选, 此时不用 CheckBox, 而用 RadioButton
5.2 BaseCheckedItem
它是 BaseItem 的实现类, 并增加了 hasChecked 字段
/** * 普通单多选列表 实体类 */ interface BaseCheckedItem : BaseItem{ var hasChecked: Boolean // 是否被勾选中 }
5.3 实体类
class CheckEntity( var name: String? = null, override var hasChecked: Boolean = false, override var hasChanged: Boolean = false) : BaseCheckedItem
5.4 重点来了: SingleChoiceAdapter
直接上代码吧, 已经写了注释
class SingleChoiceAdapter<T : BaseCheckedItem>( /** * 布局id; */ private val layout: Int, handler: BaseHandler? = null ): BaseAdapter<T>(handler), CompoundButton.OnCheckedChangeListener { //弱引用, 当选中条目被删除时, 应当释放资源 var wearEntity: WeakReference<T>? = null var wearView: WeakReference<CompoundButton>? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder { val bingding: ViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), layout, parent, false) // 统一规范 CheckBox 的id; 将 RecyclerView 存入 ChoiceHolder val cbCheck = bingding.root.findViewById<CompoundButton>(R.id.cb_check_item) return ChoiceHolder(cbCheck, bingding, handler) } override fun onBindViewHolder(holder: NewViewHolder, position: Int) { if(holder is ChoiceHolder){ val entity = currentList[position] if(entity.hasChecked){ wearEntity = WeakReference(entity) wearView = WeakReference(holder.cbCheck) } //用 tag 将对象实体存储; holder.cbCheck?.tag = entity holder.cbCheck?.setOnCheckedChangeListener(this) //这一行是测试用的, 应当删掉 (entity as CheckEntity).name = Integer.toHexString(holder.cbCheck?.hashCode() ?: 0) } super.onBindViewHolder(holder, position) } /** * checkbox 选中事件 */ override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { //判断是否是 手动操作 if(buttonView?.isPressed == true){ Log.d("pppppppppppppppppp", "手动操作了 ChekcBox") wearEntity?.let { it.get()?.hasChecked = false it.clear() } // 因 RecycleView 复用机制, 这里新旧 Button 可能为同一个; // 当同一个button 时, 不需要重置原button 的选中状态; 不需要重新 new WeakReference val isSameBtn = buttonView == wearView?.get() if(!isSameBtn){ wearView?.let { it.get()?.isChecked = false it.clear() } wearView = WeakReference(buttonView) } val entity = buttonView.tag as T? entity?.hasChecked = true wearEntity = WeakReference(entity) } } /** * 重写 ViewHoleder, 附带 ImageView; 勾选是使用 ImageView 切换图片的方式 */ class ChoiceHolder(val cbCheck: CompoundButton?, binding: ViewDataBinding, handler: BaseHandler?) : NewViewHolder(binding, handler) }
5.5 布局文件: item_test_choise
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="item" type="com.example.kotlinmvpframe.yiyou.entity.CheckEntity" /> <variable name="handler" type="com.example.kotlinmvpframe.test.testtwo.Handler" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:text="@{item.name}" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"/> <RadioButton android:id="@+id/cb_check_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableEnd="@drawable/checkbox_selector" android:padding="24dp" android:background="@null" android:button="@null" android:checked="@{item.hasChecked}" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
5.6 使用:
很简单, 只需要 初始化 Adapter, RecycleView; 并设置数据源即可
mAdapter = SingleChoiceAdapter(R.layout.item_test_choise) mDataBind.rvRecycle.let { it.layoutManager = LinearLayoutManager(mActivity) it.adapter = mAdapter } val data = mutableListOf<CheckEntity>() repeat(15){ data.add(CheckEntity("小华")) } mAdapter.submitList(data) //以下, 没用 mDataBind.btnLeft.setOnClickListener { val entity = mAdapter.wearEntity?.get() if(entity == null){ Toast.makeText(mActivity, "当前未选中", Toast.LENGTH_SHORT).show() return@setOnClickListener } val position = mAdapter.currentList.indexOf(entity) Toast.makeText(mActivity, "当前选中${entity.name}, index=${position}", Toast.LENGTH_SHORT).show() } mDataBind.btnRight.setOnClickListener { mAdapter.notifyDataSetChanged() }
5.7 效果图:
6. MultipleChoiceAdapter 多选
多选就简单了; 注意, 需要将 item_test_choise 中的 RadioButton 换成 CheckBox
6.1 直接上代码:
class MultipleChoiceAdapter<T : BaseCheckedItem>( /** * 布局id; */ private val layout: Int, handler: BaseHandler? = null ): BaseAdapter<T>(handler), CompoundButton.OnCheckedChangeListener { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder { val bingding: ViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), layout, parent, false) // 统一规范 CheckBox 的id; 将 RecyclerView 存入 NestedHolder val cbCheck = bingding.root.findViewById<CompoundButton>(R.id.cb_check_item) return SingleChoiceAdapter.ChoiceHolder(cbCheck, bingding, handler) } override fun onBindViewHolder(holder: NewViewHolder, position: Int) { if(holder is SingleChoiceAdapter.ChoiceHolder){ val entity = currentList[position] holder.cbCheck?.tag = entity holder.cbCheck?.setOnCheckedChangeListener(this) } super.onBindViewHolder(holder, position) } /** * checkbox 选中事件 */ override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { //判断是否是 手动操作 if(buttonView?.isPressed == true){ val entity = buttonView.tag as T? entity?.hasChecked = isChecked } } }
6.2 使用跟 单选Adapter一样, 初始化RecycleVIew, 设置数据源即可;
遍历的话这样:
val list = mAdapter.currentList.filter { it.hasChecked } Toast.makeText(mActivity, "当前选中${list.size}条", Toast.LENGTH_SHORT).show()
6.3 效果图
好的! 终于 over