博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Kotlin结合DataBinding简单封装一个RecyclerView的Adapter
阅读量:7019 次
发布时间:2019-06-28

本文共 5501 字,大约阅读时间需要 18 分钟。

这是项目总结的第二篇,上一篇在这:

在 中我尝试使用 DataBinding 来简化代码,本来是使用 这个优秀的开源框架作为基础 RecyclerView 的 Adapter使用的,但是写的过程中发现在 Adapter 里每次要写一堆样板代码,想着既然是实验项目,就干脆自己封装一个好了。

本篇blog主要是记录一下当时的思路和关于 Kotlin 的一些泛型知识。

需要哪些功能

  1. 通过最简单的方式的绑定数据
  2. item的点击事件
  3. 分页和预加载

简单的成果展示

下面这个 Adapter 是项目里代码最少的一个

class DataTypeAdapter(items: MutableList
) : BaseAdapter
(items, R.layout.item_type_adapter) { override fun bindItem(binding: ItemTypeAdapterBinding, item: Data.Results) { binding.data = item }}复制代码

实现的视图效果:

DataBinding 的预备知识

  1. 首先我们都知道当我们用 DataBinding 写完一个布局之后会生成一个相应的Binding类,比如布局文件是 R.layout.item_feed_adapter, 对应的类则是 ItemFeedAdapterBinding,而所有 Binding 类都继承自 ViewDataBinding 这个抽象类
  2. 根据官网文档,绑定视图的方式有两种:
    val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)// orval listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)复制代码
    由于 ViewDataBinding 这个类没有 inflate 方法, 我们选择第二种方法
  3. 根据官网文档,对于单个布局绑定数据只有一种方式,比如你在布局里写了这样一个东西:
    复制代码
    那Binding类里就会生成这样一个方法:
    public abstract void setAbc(@Nullable Results abc);复制代码
    那绑定数据就只能这样:
    binding.abc = Results()复制代码
    可以看出来,完 全 没 有 规 律,所以这个绑定数据的过程必须要抽出来让子类自己实现了。
    (文档里其实还有一种 setVariable 的方式绑定数据,不过那个是用来共享变量名的,这里用不到)

实现

  1. 基于上面的分析,我们可以得到初步封装好的代码:

    abstract class BaseAdapter
    (private var items: MutableList
    , private var layoutRes: Int) : RecyclerView.Adapter
    .ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = DataBindingUtil.inflate
    (inflater, layoutRes, parent, false) return ViewHolder(binding) } override fun getItemCount() = items.size override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(items[position]) } fun setNewData(newItems: List
    ) { items.clear() items.addAll(newItems) notifyDataSetChanged() } inner class ViewHolder(private val binding: B) : RecyclerView.ViewHolder(binding.root) { fun bind(item: T) { bindItem(binding, item) binding.executePendingBindings() } } abstract fun bindItem(binding: B, item: T)}复制代码

    这里关于泛型B,要不要声明协变,即 out B:ViewDataBinding, 看起来这个类在 adapter 里是个生产者,可以写,但其实不用,因为Binding类都是自动生成的,互相之间不会有继承关系,不会出现子类 adapter 赋值给父类 adapter 的代码。

  2. 为防止绑定数据没有及时生效,我们最好不要把业务代码一起写在 bindItem 方法里,所以再抽出来一个方法 bindAfterExecute 用来写额外的业务,比如处理图片,不声明成 abstract 的原因是这个方法不是必须要实现的

    inner class ViewHolder(private val binding: B) : RecyclerView.ViewHolder(binding.root) {    fun bind(item: T) {        bindItem(binding, item)        binding.executePendingBindings()        bindAfterExecute(binding, item)    }    fun getBinding() = binding}abstract fun bindItem(binding: B, item: T)open fun bindAfterExecute(binding: B, item: T) {}复制代码
  3. 再加上 Item 点击事件 声明一个 lambda 变量和参数为一个 lambda 的点击方法

    private var itemClickListener: ((item: T, binding: B) -> Unit)? = null...fun setItemClick(click: (item: T, binding: B) -> Unit) {    itemClickListener = click}复制代码

    然后在 ViewHolder 的 bind 里调用

    fun bind(item: T) {    ...    binding.root.setOnClickListener {        itemClickListener?.invoke(item, binding)    }    ...}复制代码

    调用示例:

    adapter.setItemClick { item, _ ->    ARouter.getInstance().build("/home/webActivity")        .withString("web_url", item.url)        .withString("title", item.title)        .withString("cover_url", item.cover)        .withBoolean("isArticle", true)        .navigation()}复制代码

    那么问题来了,如果要对视图中某个view暴露点击事件该怎么办,思路和上面的实现方法一样,添加不同的 Lambda 就行了。

  4. 加上分页和预加载功能,这里因为项目里只有 LinearLayoutManager 的 RecyclerView,所以没对其他布局兼容,而且预加载的时机我直接写死是剩10条了(因为懒),注意要对 RecyclerView 滑动中和停止两种状态都做监听。setLoadComplete() 和 setLoadFail() 的实现完全一样是因为本来我想写个footer之类的东西展示失败的视图,就分成了两个方法,后来因为懒没写。

    private var isLoading = falsefun setLoadMoreListener(recyclerView: RecyclerView, loader: () -> Unit) {    recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {        override fun onScrolled(ry: RecyclerView, dx: Int, dy: Int) {            super.onScrolled(ry, dx, dy)            val lastVisibleItem = (ry.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()            val totalItemCount = ry.layoutManager.itemCount            if (lastVisibleItem >= totalItemCount - 10 && dy > 0) {                if (!isLoading) {                    loader()                    isLoading = true                }            }        }        override fun onScrollStateChanged(ry: RecyclerView, newState: Int) {            super.onScrollStateChanged(ry, newState)            val lastVisibleItem = (ry.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()            val totalItemCount = ry.layoutManager.itemCount            if (lastVisibleItem >= totalItemCount - 10 && newState == 0) {                if (!isLoading) {                    loader()                    isLoading = true                }            }        }    })}fun setLoadComplete() {    isLoading = false}fun setLoadFail() {    isLoading = false}fun addData(data: List
    ) { items.addAll(data) notifyItemRangeInserted(items.size - data.size, data.size)}复制代码

    调用示例:

    adapter.setLoadMoreListener(ryc_main) {    page++    viewModel.requestData(page)}...viewModel.getFeed().observe(this, Observer {    it?.let { data ->        if (page == 1) {            adapter.setNewData(data)        } else {            adapter.addData(data)            adapter.setLoadComplete()        }    }    ...})复制代码

总结

虽然这个东西很简单,实际上确实很简单。。。(强行水了一篇blog好惭愧)

完整的代码戳这里

转载地址:http://nazxl.baihongyu.com/

你可能感兴趣的文章
Java并发编程系列之四:volatile和锁的内存语义
查看>>
(一一九)类作用域
查看>>
MFC学习笔记之一(绘图+控制)
查看>>
C++语言之动态内存分配
查看>>
PgSQL · 答疑解惑 · PostgreSQL 9.6 并行查询实现分析
查看>>
分布式系统的理解
查看>>
微服务熔断与隔离
查看>>
html中link的用法
查看>>
RSA非对称加密
查看>>
【基础】利用 hexo + Gitpage 开发自己的博客
查看>>
JPA/Hibernate/Spring Data概念
查看>>
Vue Render介绍和一些基本的实例
查看>>
oracle业务硬盘出现故障无法访问,提示需要重新格式化后解决方法
查看>>
阿里云腾讯云服务器部署安装 Mysql5.7.20 【三部曲之一】
查看>>
webpack打包合并
查看>>
[转] 如何实现 React 写小程序-1
查看>>
iOS开发之 Method Swizzling 深入浅出
查看>>
百万开发师讲解:iOS性能优化
查看>>
简单的聊聊索引的那些事儿
查看>>
大快搜索数据爬虫技术实例安装教学
查看>>