/ 今日科技快讯 /
发现一件很有意思的事情,今天这篇文章中介绍的RecyclerView,以及昨天文章中介绍的Lifecycles,它们共同的作者都是前天文章中介绍的Yigit Boyar大神。确实不是我有意为之,我都是按照投稿的顺序来安排推送的。而Yigit Boyar大神明天将会做客上海GDG,与大家进行一场问答式的技术活动。这种跟Google大神零距离接触的机会可不多,希望大家到时都能准时观看,我们明天见。/ 作者简介 /本篇文章来自秦川小将的投稿,给大家分享了如何使用RecyclerView实现复杂的列表,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章! 秦川小将的博客地址:
https://blog.csdn.net/mjb00000
/ 前言 /在RecyclerView实现多种Item类型列表时,有很多种实现方式,这里结合 AsyncListDiffer+DataBinding+Lifecycles 实现一种简单,方便,快捷并以数据驱动UI变化的MultiTypeAdapter
AsyncListDiffer 一个在后台线程中使用DiffUtil计算两组新旧数据之间差异性的辅助类。
DataBinding 以声明方式将可观察的数据绑定到界面元素。
Lifecycles 管理您的 Activity 和 Fragment 生命周期。
/ 效果图 /
定义一个基类MultiTypeBinder方便统一实现与管理MultiTypeBinder中部分函数说明:
layoutId():初始化xml。
areContentsTheSame():该方法用于数据内容比较,比较两次内容是否一致,刷新UI时用到。
onBindViewHolder(binding: V):与RecyclerView.Adapter中的onBindViewHolder方法功能一致,在该方法中做一些数据绑定与处理,不过这里推荐使用DataBinding去绑定数据,以数据去驱动UI。
onUnBindViewHolder():该方法处理一些需要释放的资源。
继承MultiTypeBinder后进行Layout初始化和数据绑定及解绑处理。
abstract class MultiTypeBinder : ClickBinder() {
/**
* BR.data
*/
protected open val variableId = BR.data
/**
* 被绑定的ViewDataBinding
*/
open var binding: V? = null
/**
* 给绑定的View设置tag
*/
private var bindingViewVersion = (0L until Long.MAX_VALUE).random()
/**
* 返回LayoutId,供Adapter使用
*/
@LayoutRes
abstract fun layoutId(): Int
/**
* 两次更新的Binder内容是否相同
*/
abstract fun areContentsTheSame(other: Any): Boolean
/**
* 绑定ViewDataBinding
*/
fun bindViewDataBinding(binding: V) {
// 如果此次绑定与已绑定的一至,则不做绑定
if (this.binding === binding && binding.root.getTag(R.id.bindingVersion) == bindingViewVersion) return
binding.root.setTag(R.id.bindingVersion, ++bindingViewVersion)
onUnBindViewHolder()
this.binding = binding
binding.setVariable(variableId, this)
// 给 binding 绑定生命周期,方便观察LiveData的值,进而更新UI。如果不绑定,LiveData的值改变时,UI不会更新
if (binding.root.context is LifecycleOwner) {
binding.lifecycleOwner = binding.root.context as LifecycleOwner
} else {
binding.lifecycleOwner = AlwaysActiveLifecycleOwner()
}
onBindViewHolder(binding)
// 及时更新绑定数据的View
binding.executePendingBindings()
}
/**
* 解绑ViewDataBinding
*/
fun unbindDataBinding() {
if (this.binding != null) {
onUnBindViewHolder()
this.binding = null
}
}
/**
* 绑定后对View的一些操作,如:赋值,修改属性
*/
protected open fun onBindViewHolder(binding: V) {
}
/**
* 解绑操作
*/
protected open fun onUnBindViewHolder() {
}
/**
* 为 Binder 绑定生命周期,在 {@link Lifecycle.Event#ON_RESUME} 时响应
*/
internal class AlwaysActiveLifecycleOwner : LifecycleOwner {
override fun getLifecycle(): Lifecycle = object : LifecycleRegistry(this) {
init {
handleLifecycleEvent(Event.ON_RESUME)
}
}
}
}
在values中定义一个ids.xml文件,给 ViewDataBinding 中的 root View设置Tag。
处理MultiTypeBinder中View的点击事件
在ClickBinder中提供了两种事件点击方式 onClick 和 onLongClick,分别提供了携带参数和未带参数方法。
open class ClickBinder: OnViewClickListener {
protected open var mOnClickListener: ((view: View, any: Any?) -> Unit)? = null protected open var mOnLongClickListener: ((view: View, any: Any?) -> Unit)? = null /** * 设置View点击事件 */ open fun setOnClickListener(listener: (view: View, any: Any?) -> Unit): ClickBinder { this.mOnClickListener = listener return this } /** * 设置View长按点击事件 */ open fun setOnLongClickListener(listener: (view: View, any: Any?) -> Unit): ClickBinder { this.mOnLongClickListener = listener return this } /** * 触发View点击事件时回调,携带参数 */ override fun onClick(view: View) { onClick(view, this) } override fun onClick(view: View, any: Any?) { if (mOnClickListener != null) { mOnClickListener?.invoke(view, any) } else { if (BuildConfig.DEBUG) throw NullPointerException("OnClick事件未绑定!") } } /** * 触发View长按事件时回调,携带参数 */ override fun onLongClick(view: View) { onLongClick(view, this) } override fun onLongClick(view: View, any: Any?){ if (mOnLongClickListener != null) { mOnLongClickListener?.invoke(view, any) } else { throw NullPointerException("OnLongClick事件未绑定!") } }
}
使用DiffUtil.ItemCallback进行差异性计算
在刷新列表时这里使用了DiffUtil.ItemCallback来做差异性计算,方法说明:
areItemsTheSame(oldItem: T, newItem: T):比较两次MultiTypeBinder是否时同一个Binder
areContentsTheSame(oldItem: T, newItem: T):比较两次MultiTypeBinder的类容是否一致。
class DiffItemCallback<T : MultiTypeBinder<*>> : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = oldItem.layoutId() == newItem.layoutId()
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = oldItem.hashCode() == newItem.hashCode() && oldItem.areContentsTheSame(newItem)
}
定义MultiTypeAdapter在MultiTypeAdapter中的逻辑实现思路如下:
使用 LinkedHashMap 来存储每个 Binder 和 Binder 对应的 Type 值,确保顺序。
在 getItemViewType(position: Int) 函数中添加 Binder 类型
在 onCreateViewHolder(parent: ViewGroup, viewType: Int) 方法中对 Binder 的 Layout 进行初始化,其中 inflateDataBinding 为 Kotlin 扩展,主要是将 Layout 转换为一个 ViewDataBinding 的对象。
在 onBindViewHolder(holder: MultiTypeViewHolder, position: Int) 方法中调用 Binder 中的绑定方法,用以绑定数据。
使用 AsyncListDiffer 工具返回当前列表数据和刷新列表,具体用法下文说明
// 这里将LayoutManager向外扩展,方便操作RecyclerView滚动平移等操作
class MultiTypeAdapter constructor(val layoutManager: RecyclerView.LayoutManager): RecyclerView.Adapter() {
// 使用后台线程通过差异性计算来更新列表 private val mAsyncListChange by lazy { AsyncListDiffer(this, DiffItemCallback<MultiTypeBinder<*>>()) } // 存储 MultiTypeBinder 和 MultiTypeViewHolder Type private var mHashCodeViewType = LinkedHashMap<Int, MultiTypeBinder<*>>() init { setHasStableIds(true) } fun notifyAdapterChanged(binders: List<MultiTypeBinder<*>>) { mHashCodeViewType = LinkedHashMap() binders.forEach { mHashCodeViewType[it.hashCode()] = it } mAsyncListChange.submitList(mHashCodeViewType.map { it.value }) } fun notifyAdapterChanged(binder: MultiTypeBinder<*>) { mHashCodeViewType = LinkedHashMap() mHashCodeViewType[binder.hashCode()] = binder mAsyncListChange.submitList(mHashCodeViewType.map { it.value }) } override fun getItemViewType(position: Int): Int { val mItemBinder = mAsyncListChange.currentList[position] val mHasCode = mItemBinder.hashCode() // 如果Map中不存在当前Binder的hasCode,则向Map中添加当前类型的Binder if (!mHashCodeViewType.containsKey(mHasCode)) { mHashCodeViewType[mHasCode] = mItemBinder } return mHasCode } override fun getItemId(position: Int): Long = position.toLong() override fun getItemCount(): Int = mAsyncListChange.currentList.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiTypeViewHolder { try { return MultiTypeViewHolder(parent.inflateDataBinding(mHashCodeViewType[viewType]?.layoutId()!!)) }catch (e: Exception){ throw NullPointerException("不存在${mHashCodeViewType[viewType]}类型的ViewHolder!") } } @Suppress("UNCHECKED_CAST") override fun onBindViewHolder(holder: MultiTypeViewHolder, position: Int) { val mCurrentBinder = mAsyncListChange.currentList[position] as MultiTypeBinder<ViewDataBinding> holder.itemView.tag = mCurrentBinder.layoutId() holder.onBindViewHolder(mCurrentBinder) }
}
定义扩展Adapters文件
/**
创建一个MultiTypeAdapter
*/
fun createMultiTypeAdapter(recyclerView: RecyclerView, layoutManager: RecyclerView.LayoutManager): MultiTypeAdapter {
recyclerView.layoutManager = layoutManager
val mMultiTypeAdapter = MultiTypeAdapter(layoutManager)
recyclerView.adapter = mMultiTypeAdapter
// 处理RecyclerView的触发回调
recyclerView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View?) {
mMultiTypeAdapter.onDetachedFromRecyclerView(recyclerView)
}
override fun onViewAttachedToWindow(v: View?) { }
})
return mMultiTypeAdapter
}
/**
MultiTypeAdapter扩展函数,重载MultiTypeAdapter类,使用invoke操作符调用MultiTypeAdapter内部函数。
*/
inline operator fun MultiTypeAdapter.invoke(block: MultiTypeAdapter.() -> Unit): MultiTypeAdapter {
this.block()
return this
}
/**
将Layout转换成ViewDataBinding
*/
fun ViewGroup.inflateDataBinding(layoutId: Int): T = DataBindingUtil.inflate(LayoutInflater.from(context), layoutId, this, false)!!
/**
RecyclerView方向注解
*/
@IntDef(
Orientation.VERTICAL,
Orientation.HORIZONTAL
)
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
annotation class Orientation{
companion object{
const val VERTICAL = RecyclerView.VERTICAL
const val HORIZONTAL = RecyclerView.HORIZONTAL
}
}
MultiTypeAdapter使用
创建 MultiTypeAdapter