- 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 中。
想了解小夥伴 點這裡
recyclerview = '1.2.0'
implementation androidx.recyclerview:recyclerview:${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