乐趣区

Kotlin自定义委托属性应用的小例子

接上篇

自定义属性委托的用处很多,例如组合替代继承,给个 ViewBindingFragment中的使用的例子:

委托:

/**
 * 自定义属性委托。*
 * <p>......。</p>
 * <ul><li></li></ul>
 * <br>
 * <strong>Time</strong>&nbsp;&nbsp;&nbsp;&nbsp;2020/5/18 14:54<br>
 * <strong>CopyRight</strong>&nbsp;&nbsp;&nbsp;&nbsp;2020, tt.reducto<br>
 *
 * @version  : 1.0.0
 * @author   : TT
 */

fun <T> Fragment.viewBindingByLazy(initialise: () -> T): ReadOnlyProperty<Fragment, T> =
    object : ReadOnlyProperty<Fragment, T>, DefaultLifecycleObserver {

        private var binding: T? = null

        private var viewLifecycleOwner: LifecycleOwner? = null
        private val mainHandler = Handler(Looper.getMainLooper())


        init {
            this@viewBindingByLazy
                .viewLifecycleOwnerLiveData
                .observe(this@viewBindingByLazy, Observer { newLifecycleOwner ->
                    viewLifecycleOwner
                        ?.lifecycle
                        ?.removeObserver(this)

                    viewLifecycleOwner = newLifecycleOwner.also {it.lifecycle.addObserver(this)
                    }
                })
        }
        @MainThread
        override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)
            mainHandler.post {binding = null}
        }
        @MainThread
        override fun getValue(
            thisRef: Fragment,
            property: KProperty<*>
        ): T {return this.binding ?: initialise().also {this.binding = it}
        }
    }

使用:

class MultipleFragment : Fragment(R.layout.fragment_multiple) {
    .....
    private val binding: FragmentMultipleBinding by viewBindingByLazy {
        FragmentMultipleBinding.bind(requireView()
        )
    }
    .....
}

注意添加依赖:

 implementation  "androidx.lifecycle:lifecycle-common-java8:2.2.0"

那我们这里同样利用自定义属性委托让 ViewHolder 可以存储和检索任何类型的任何属性

by Map 委托

/**
 *
 * @receiver View
 * @param id Int
 * @param initializer Function0<T>
 * @return T 存储容器 -> map
 */
private inline fun <reified T> View.getOrPutTag(@IdRes id: Int, initializer: () -> T) =
    getTag(id) as? T ?: initializer().also {setTag(id, it)
    }
    
    
 /**
 *  
 */
private inline val BindingViewHolder<*>.propertyByMap
    get() = itemView.getOrPutTag<MutableMap<String, Any?>>(
        R.id.recyclerview_view_binding_map,
        ::mutableMapOf
    )

避免重名


/**
 * 用于 ViewBinding 的通用 ViewHolder。*  
 * <p> 对外提供数据与视图的匹配。</p>
 * <ul><li></li></ul>
 * <br>
 * <strong>Time</strong>&nbsp;&nbsp;&nbsp;&nbsp;2020/5/18 12:38<br>
 * <strong>CopyRight</strong>&nbsp;&nbsp;&nbsp;&nbsp;2020, tt.reducto<br>
 *
 * @version  : 1.0.0
 * @author   : TT
 */
open class BindingViewHolder<T : ViewBinding> private constructor(val binding: T) :
    RecyclerView.ViewHolder(binding.root) {
    constructor(
        parent: ViewGroup,
        creator: (inflater: LayoutInflater, root: ViewGroup, attachToRoot: Boolean) -> T
    ) : this(
        creator(LayoutInflater.from(parent.context),
            parent,
            false
        )
    )

    /**
     *  委托
     *
     * @param T itemView 适配数据。*/
    class BindingDataLazy<T> : ReadWriteProperty<BindingViewHolder<*>, T> {override fun getValue(thisRef: BindingViewHolder<*>, property: KProperty<*>): T {@Suppress("UNCHECKED_CAST")
            return thisRef.propertyByMap[property.name] as T
        }

        override fun setValue(thisRef: BindingViewHolder<*>, property: KProperty<*>, value: T) {thisRef.propertyByMap[property.name] = value
        }
    }
}

/**
 *
 * @receiver ViewGroup
 * @param creator 布局生成器
 * @return BindingViewHolder<T>
 */
fun <T : ViewBinding> ViewGroup.getViewHolder(creator: (inflater: LayoutInflater, root: ViewGroup, attachToRoot: Boolean) -> T
): BindingViewHolder<T> =
    BindingViewHolder(this, creator)

/**
 *
 */
private inline val BindingViewHolder<*>.propertyByMap
    get() = itemView.getOrPutTag<MutableMap<String, Any?>>(
        R.id.recyclerview_view_binding_map,
        ::mutableMapOf
    )

/**
/**
 * getTag()内部使用 { <code>private SparseArray<Object> mKeyedTags;</code>} 进行查找
 *
 * @receiver View
 * @param id Int 保证 ID 唯一
 * @param initializer 容器初始化
 * @return T 存储容器 -> map
 */
private inline fun <reified T> View.getOrPutTag(@IdRes id: Int, initializer: () -> T) =
    getTag(id) as? T ?: initializer().also {setTag(id, it)
    }

ViewPage2 的 Adapter 写个简单item_multiple.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:card_view="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.cardview.widget.CardView
        android:id="@+id/cd_item_multi"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/pageMarginAndOffset"
        android:layout_marginRight="@dimen/pageMarginAndOffset"
        android:background="@color/cardview_light_background"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        app:cardUseCompatPadding="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        card_view:cardCornerRadius="6dp">

        <ImageView
            android:id="@+id/img_item_multi"
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:scaleType="fitXY" />
    </androidx.cardview.widget.CardView>

    <TextView
        android:id="@+id/tv_item_multi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="1111"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cd_item_multi" />

    <CheckBox
        android:id="@+id/ck_item_multi"
        app:layout_constraintTop_toBottomOf="@id/cd_item_multi"
        app:layout_constraintEnd_toStartOf="@+id/tv_item_multi"
        android:layout_width="48dp"
        android:layout_height="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

数据源:

data class Category(val name: String,val id:Int)

Fragment 或者 Activity 中:

private var BindingViewHolder<ItemMultipleBinding>.result by BindingViewHolder.BindingDataLazy<Category>()

fun BindingViewHolder<ItemMultipleBinding>.bindView(data: Category) {
    result = data
    binding.tvItemMulti.text = result.name
    binding.imgItemMulti.load(result.id)
    binding.ckItemMulti.setOnCheckedChangeListener {buttonView, isChecked ->}
}

如何与 Adapter 结合?我们利用闭包写个简单的 Adapter:

class BindingAdapter <ItemT : Any?, VH : RecyclerView.ViewHolder>(private val itemsData: () -> List<ItemT>,
    private val viewHolderCreator: (ViewGroup, Int) -> VH,
    private val viewHolderBinder: (holder: VH, item: ItemT, position: Int) -> Unit,
    private val itemIdFunction: ((ItemT) -> Long)? = null
) : RecyclerView.Adapter<VH>() {

    init {setHasStableIds(itemIdFunction != null)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = viewHolderCreator(parent, viewType)

    override fun onBindViewHolder(holder: VH, position: Int) = viewHolderBinder(holder, itemsData()[position], position)

    override fun getItemCount(): Int = itemsData().size


    override fun getItemId(position: Int): Long =
        itemIdFunction?.invoke(itemsData()[position]) ?: super.getItemId(position)


}

fun <ItemT, VH : RecyclerView.ViewHolder> adapterByBinding(itemsData: () -> List<ItemT>,
    viewHolderCreator: (ViewGroup, Int) -> VH,
    viewHolderBinder: (holder: VH, item: ItemT, position: Int) -> Unit,
    itemIdFunction: ((ItemT) -> Long)? = null
): RecyclerView.Adapter<VH> =
    BindingAdapter(
        itemsData,
        viewHolderCreator,
        viewHolderBinder,
        itemIdFunction
    )

如何使用?

 binding.vpMultiple.apply {
            // 横向滑动
            orientation = ViewPager2.ORIENTATION_HORIZONTAL
            //
            clipToPadding = false
            //
            clipChildren = false
            // 可见页面数
            offscreenPageLimit = 3
                        
            adapter = adapterByBinding(itemsData = Test()::mCategoryList,
                viewHolderCreator = { viewGroup, _ ->
                    viewGroup.getViewHolder(ItemMultipleBinding::inflate).apply {
                        itemView.setOnClickListener {toast("${this.adapterPosition}")
                        }
                    }
                },
                viewHolderBinder = { viewHolder, data, _ ->
                    viewHolder.bindView(data)
                } 
            )
    
      .......     
  }
  
  
private var BindingViewHolder<ItemMultipleBinding>.result by BindingViewHolder.BindingDataLazy<Category>()

fun BindingViewHolder<ItemMultipleBinding>.bindView(data: Category) {
    result = data
    binding.tvItemMulti.text = result.name
    binding.imgItemMulti.load(result.id)
    binding.ckItemMulti.setOnCheckedChangeListener {buttonView, isChecked ->}
}

class Test {val mCategoryList: MutableList<Category> = mutableListOf()
    private val ids = arrayListOf(
        R.mipmap.image1,
        R.mipmap.image2,
        R.mipmap.image3,
        R.mipmap.image4,
        R.mipmap.image5
    )

    init {for (i in ids.indices) {mCategoryList.add(i, Category("page_${i + 1}", ids[i]))
        }

    }
}

如果需要支持 DiffUtil 或者多 ItemType 等等其他也可以加上,不过有了 MergeAdapter 更方便

参考:

ViewBindingPropertyDelegate

view-binding-internals

退出移动版