接上篇
自定义属性委托的用处很多,例如组合替代继承,给个 ViewBinding
在Fragment
中的使用的例子:
委托:
/**
* 自定义属性委托。*
* <p>......。</p>
* <ul><li></li></ul>
* <br>
* <strong>Time</strong> 2020/5/18 14:54<br>
* <strong>CopyRight</strong> 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> 2020/5/18 12:38<br>
* <strong>CopyRight</strong> 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