如何使用RecyclerView优雅地实现复杂列表效

简介: / 今日科技快讯 /发现一件很有意思的事情,今天这篇文章中介绍的RecyclerView,以及昨天文章中介绍的Lifecycles,它们共同的作者都是前天文章中介绍的Yigit Boyar大神。确实不是我有意为之,我都是按照投稿的顺序来安排推送的。而Yigit Boyar大神明天将会做客上海GDG,与大家进行一场问答式的技术活动。这种跟Google大神零距离接触的机会可不多,希望大家到时都能准时观看,我们明天见。/ 作者简介 /本篇文章来自秦川小将的投稿,给大家分享了如何使用RecyclerView实现复杂的列表,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章! 秦川小将的博客地址:

/ 今日科技快讯 /

发现一件很有意思的事情,今天这篇文章中介绍的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


相关文章
|
人工智能 编解码
国内原汁原味的免费sd训练工具--哩布哩布AI
国内原汁原味的免费sd训练工具--哩布哩布AI
2013 0
|
数据库 UED Python
1、基于python多进程+pyqt5开发流畅界面程序
使用python+pyqt5开发界面程序,利用多进程分离界面和任务执行功能,达到界面流畅不卡顿的要求。 本文程序示例:https://github.com/AlvinsFish/UiExample
2585 0
1、基于python多进程+pyqt5开发流畅界面程序
|
fastjson
fastjson设置指定日期属性的格式化
fastjson默认将时间格式化为时间戳,如果我们想以时间字符串格式输出的话,暂提供两种方式 1.序列化器方式 声明DateJsonSerializer public class DateJsonSerializer implements Object...
6990 1
|
存储 JSON 安全
深入理解与实践:Token的使用及其在Web应用安全中的重要性
【7月更文挑战第3天】在现代Web应用程序中,Token作为一种关键的安全机制,扮演着维护用户会话安全、验证用户身份的重要角色。本文将深入探讨Token的基本概念、类型、工作原理,并通过实际代码示例展示如何在Web应用中实现Token的生成、验证及应用,以确保数据传输的安全性和用户认证的有效性。
3240 2
|
JavaScript Java 测试技术
基于Java的图书馆书库管理系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的图书馆书库管理系统的设计与实现(源码+lw+部署文档+讲解等)
167 0
|
定位技术 图形学 开发者
【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)
【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)
1536 1
|
JavaScript 网络架构 开发者
阿珊解说Vue中`$route`和`$router`的区别
阿珊解说Vue中`$route`和`$router`的区别
|
算法 开发工具 Android开发
Android-图片压缩详解:原理、方法与实践
前言 在Android应用开发中,处理图片是一个非常常见的需求。然而,大尺寸和高质量的图片可能会占用大量内存,导致应用程序性能下降,甚至引发OOM(Out of Memory)错误。因此,对图片进行合适的压缩是非常重要的。本文将详细介绍Android图片压缩的原理、方法和实践。
871 0
|
数据可视化 C# 图形学
【unity造轮子】Unity ShaderGraph使用教程与各种特效案例
点关注不迷路,持续输出干货文章。 嗨,大家好,我是向宇。最近在玩ShaderGraph,决定把我自己实验的所有效果记录到这篇博客中,附带完整高清的连线动态图,希望对想要学习ShaderGraph的同学有所启发。后续有发现一些新的ShaderGraph我还会继续进行更新。
864 0
|
SQL JSON JavaScript
为什么从egg.js到nest.js(一)
进入部门工作后,接触到的node.js服务端框架,是egg.js,后面基于扩展增加了很多插件,比如:@Controller @Service等注解,还有针对egg-framework 定制化部门使用的底层framework。
635 0