孟老闆 ListAdapter封裝, 告別Adapter程式碼 (中)

孟老闆發表於2021-06-15

ListAdapter封裝 (中) - 多條目, 頭尾, 巢狀, 單多選. 

前言:

上一篇文章已經講解 SimpleAdapter 的基本封裝.  這次我們將用 ConcatAdapter 封裝頭尾,  並封裝多型別Adapter,  巢狀RecycleView, 單多選Adapter; 

 

目錄: 

  1. 用 ConcatAdapter 封裝頭尾;
  2. 用泛型, 改建 BaseAdapter
  3. 多條目 MultipleAdapter
  4. 巢狀  NestedAdapter
  5. 單選  SingleChoice
  6. 多選  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  分析: 

  1. 實體需要有 checked 欄位, 我們用 BaseCheckedItem 作為實體基類
  2. 規範 佈局檔案中 CheckBox 的 ID,  onCreateViewHolder 時 通過 findViewById 將控制元件儲存到 ViewHolder
  3. 選中某個條目時, 之前選中的條目需要反選.  所以必須要知道上一次選中的 實體和CheckBox
  4. 監聽 onCheckedChanged 事件,  新實體選中, 就實體反選.
  5. 單選時, 不允許反選, 此時不用 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

回到頂部

相關文章