前面在介绍列表视图和网格视图时,它们的适配器代码都存在视图持有者ViewHolder,因为Android对列表类视图提供了回收机制,如果某些列表项在屏幕上看不到了,则系统会自动回收相应的视图对象。随着用户的下拉或者上拉手势,已经被回收的列表项要重新加载到界面上,倘若每次加载都得从头创建视图对象,势必增加了系统的资源开销。所以ViewHolder便应运而生,它在列表项首次初始化时,就将其视图对象保存起来,后面再次加载该视图时,即可直接从持有者处获得先前的视图对象,从而减少了系统开销,提高了系统的运行效率。
视图持有者的设计理念固然美好,却苦了Android开发者,每次由BaseAdapter派生新的适配器类,都必须手工处理视图持有者的相关逻辑,实在是个沉重的负担。有鉴于此,循环视图的适配器把视图持有者的重用逻辑剥离出来,由系统自行判断并处理持有者的重用操作。开发者继承RecyclerView.Adapter之后,只要完成业务上的代码逻辑即可,无需进行BaseAdapter视图持有者的手工重用。
现在由Kotlin实现循环视图的适配器类,综合前面两小节提到的优化技术,加上视图持有者的自动重用,适配器代码又得到了进一步的精简。由于循环视图适配器并不提供列表项的点击事件,因此开发者要自己编写包括点击、长按在内的事件处理代码。为方便理解循环适配器的Kotlin编码,下面以微信的公众号消息列表为例,给出对应的消息列表Kotlin代码:

//ViewHolder在构造时初始化布局中的控件对象
class RecyclerLinearAdapter(private val context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerView.Adapter<ViewHolder>(), OnItemClickListener, OnItemLongClickListener {val inflater: LayoutInflater = LayoutInflater.from(context)//获得列表项的数目override fun getItemCount(): Int = infos.size//创建整个布局的视图持有者override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view: View = inflater.inflate(R.layout.item_recycler_linear, parent, false)return ItemHolder(view)}//绑定每项的视图持有者override fun onBindViewHolder(holder: ViewHolder, position: Int) {val vh: ItemHolder = holder as ItemHoldervh.iv_pic.setImageResource(infos[position].pic_id)vh.tv_title.text = infos[position].titlevh.tv_desc.text = infos[position].desc// 列表项的点击事件需要自己实现vh.ll_item.setOnClickListener { v ->itemClickListener?.onItemClick(v, position)}vh.ll_item.setOnLongClickListener { v ->itemLongClickListener?.onItemLongClick(v, position)true}}//ItemHolder中的属性在构造时初始化inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {var ll_item = view.findViewById(R.id.ll_item) as LinearLayoutvar iv_pic = view.findViewById(R.id.iv_pic) as ImageViewvar tv_title = view.findViewById(R.id.tv_title) as TextViewvar tv_desc = view.findViewById(R.id.tv_desc) as TextView}private var itemClickListener: OnItemClickListener? = nullfun setOnItemClickListener(listener: OnItemClickListener) {this.itemClickListener = listener}private var itemLongClickListener: OnItemLongClickListener? = nullfun setOnItemLongClickListener(listener: OnItemLongClickListener) {this.itemLongClickListener = listener}override fun onItemClick(view: View, position: Int) {val desc = "您点击了第${position+1}项,标题是${infos[position].title}"context.toast(desc)}override fun onItemLongClick(view: View, position: Int) {val desc = "您长按了第${position+1}项,标题是${infos[position].title}"context.toast(desc)}
}

以上的适配器代码初步实现了公众号消息列表的展示页面,具体的列表效果如下图所示。

可是这个循环适配器RecyclerLinearAdapter仍然体量庞大,细细观察发现其实它有着数个与具体业务无关的属性与方法,譬如上下文对象context、布局载入对象inflater、点击监听器itemClickListener、长按监听器itemLongClickListener等等,故而完全可以把这些通用部分提取到一个基类,然后具体业务再从该基类派生出特定的业务适配器类。根据这种设计思路,提取出了循环视图基础适配器,它的Kotlin代码如下所示:

//循环视图基础适配器
abstract class RecyclerBaseAdapter<VH : RecyclerView.ViewHolder>(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), OnItemClickListener, OnItemLongClickListener {val inflater: LayoutInflater = LayoutInflater.from(context)//获得列表项的个数,需要子类重写override abstract fun getItemCount(): Int//根据布局文件创建视图持有者,需要子类重写override abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder//绑定视图持有者中的各个控件对象,需要子类重写override abstract fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)override fun getItemViewType(position: Int): Int = 0override fun getItemId(position: Int): Long = position.toLong()var itemClickListener: OnItemClickListener? = nullfun setOnItemClickListener(listener: OnItemClickListener) {this.itemClickListener = listener}var itemLongClickListener: OnItemLongClickListener? = nullfun setOnItemLongClickListener(listener: OnItemLongClickListener) {this.itemLongClickListener = listener}override fun onItemClick(view: View, position: Int) {}    override fun onItemLongClick(view: View, position: Int) {}
}

一旦有了这个基础适配器,实际业务的适配器即可由此派生而来,真正需要开发者编写的代码一下精简了不少。下面便是个循环视图的网格适配器,它实现了类似淘宝主页的网格频道栏目,具体的Kotlin代码如下所示:

//把公共属性和公共方法剥离到基类RecyclerBaseAdapter,
//此处仅需实现getItemCount、onCreateViewHolder、onBindViewHolder三个方法,以及视图持有者的类定义
class RecyclerGridAdapter(context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {override fun getItemCount(): Int = infos.sizeoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {val view: View = inflater.inflate(R.layout.item_recycler_grid, parent, false)return ItemHolder(view)}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {val vh = holder as ItemHoldervh.iv_pic.setImageResource(infos[position].pic_id)vh.tv_title.text = infos[position].title}inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {var ll_item = view.findViewById(R.id.ll_item) as LinearLayoutvar iv_pic = view.findViewById(R.id.iv_pic) as ImageViewvar tv_title = view.findViewById(R.id.tv_title) as TextView}
}

改进后的循环网格适配器,运行之后的界面效果如下图所示,无缝实现了原来需要数十行Java代码才能实现的功能。

然而基类不过是雕虫小技,Java也照样能够运用,所以这根本不入Kotlin的法眼,要想超越Java,还得拥有独门秘笈才行。注意到适配器代码仍然通过findViewById方法获得控件对象,可是号称在Anko库的支持之下,Kotlin早就无需该方法就能直接访问控件对象了呀,为啥这里依旧靠老牛拉破车呢?其中的缘由是Anko库仅仅实现了Activity活动页面的控件自动获取,并未实现适配器内部的自动获取。不过Kotlin早就料到了这一手,为此专门提供了一个插件名叫LayoutContainer,只要开发者让自定义的ViewHolder继承该接口,即可在视图持有者内部无需获取就能使用控件对象了。这下不管是在Activity代码,还是在适配器代码中,均可将控件名称拿来直接调用了。这么神奇的魔法,快来看看Kotlin的适配器代码是如何书写的:

//利用Kotlin的插件LayoutContainer,在适配器中直接使用控件对象,而无需对其进行显式声明
class RecyclerStaggeredAdapter(context: Context, private val infos: MutableList<RecyclerInfo>) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {override fun getItemCount(): Int = infos.sizeoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {val view: View = inflater.inflate(R.layout.item_recycler_staggered, parent, false)return ItemHolder(view)}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {(holder as ItemHolder).bind(infos[position])}//注意这里要去掉inner,否则运行报错“java.lang.NoSuchMethodError: No virtual method _$_findCachedViewById”class ItemHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {fun bind(item: RecyclerInfo) {iv_pic.setImageResource(item.pic_id)tv_title.text = item.title}}
}

当然,为了能够正常使用该功能,需要在适配器代码头部加上以下两行代码,其中第一行代码表示引用了Kotlin的扩展插件LayoutContainer,第二行代码与Activity的一样表示导入了指定布局文件里面所有控件对象:

import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_recycler_staggered.*

另外,因为LayoutContainer是Kotlin针对性提供给Android的扩展插件,所以需要修改模块的build.gradle,在文件末尾添加下面几行配置,表示允许引用安卓插件库:

androidExtensions {experimental = true
}

即使修改后的适配器代码用了新插件,外部仍旧同原来一样给循环视图设置适配器,调用代码并无任何变化:

    //第一种方式:使用采取了LayoutContainer的插件适配器val adapter = RecyclerStaggeredAdapter(this, RecyclerInfo.defaultStag)rv_staggered.adapter = adapter

采用了新的适配器插件,似乎已经大功告成,可是依然要书写单独的适配器代码,仔细研究发现这个RecyclerStaggeredAdapter还有三个要素是随着具体业务而变化的,包括:
1、列表项的布局文件资源编码,如R.layout.item_recycler_staggered;
2、列表项信息的数据结构名称,如RecyclerInfo;
3、对各种控件对象的设置操作,如ItemHolder类的bind方法;
除了以上三个要素,RecyclerStaggeredAdapter内部的其余代码都是允许复用的,因此,接下来的工作就是想办法把这三个要素抽象为公共类的某种变量。对于第一个的布局编码,可以考虑将其作为一个整型的输入参数;对于第二个的数据结构,可以考虑定义一个模板类,在外部调用时再指定具体的数据类;对于第三个的bind方法,若是Java编码早已束手无策,现用Kotlin编码正好将该方法作为一个函数参数传入。依照三个要素的三种处理对策,进而提炼出来了循环适配器的通用类RecyclerCommonAdapter,详细的Kotlin代码示例如下:

//循环视图通用适配器
//将具体业务中会变化的三类要素抽取出来,作为外部传进来的变量。这三类要素包括:
//布局文件对应的资源编号、列表项的数据结构、各个控件对象的初始化操作
class RecyclerCommonAdapter<T>(context: Context, private val layoutId: Int, private val items: List<T>, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder<T>>(context) {override fun getItemCount(): Int = items.sizeoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {val view: View = inflater.inflate(layoutId, parent, false)return ItemHolder<T>(view, init)}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {val vh: ItemHolder<T> = holder as ItemHolder<T>vh.bind(items.get(position))}//注意init是个函数形式的输入参数class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {fun bind(item: T) {init(view, item)}}
}

有了这个通用适配器,外部使用适配器只需像函数调用那样传入这三种变量就好了,具体调用的Kotlin代码如下所示:

    //第二种方式:使用把三类可变要素抽象出来的通用适配器val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,{view, item ->val iv_pic = view.findViewById(R.id.iv_pic) as ImageViewval tv_title = view.findViewById(R.id.tv_title) as TextViewiv_pic.setImageResource(item.pic_id)tv_title.text = item.title})rv_staggered.adapter = adapter

最终出炉的适配器仅有十行代码不到,其中的关键技术——函数参数真是不鸣则已、一鸣惊人。至此本节的适配器实现过程终于落下帷幕,一路上可谓是过五关斩六将,硬生生把数十行的Java代码压缩到不到十行的Kotlin代码,经过不断迭代优化方取得如此彪炳战绩。尤其是最后的两种实现方式,分别运用了Kotlin的多项综合技术,才能集Kotlin精妙语法之大成。

点此查看Kotlin入门教程的完整目录

__________________________________________________________________________
打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Kotlin入门(23)适配器的进阶表达相关推荐

  1. Kotlin入门(22)适配器的简单优化

    列表视图 为实现各种排列组合类的视图(包括但不限于Spinner.ListView.GridView等等),Android提供了五花八门的适配器用于组装某个规格的数据,常见的适配器有:数组适配器Arr ...

  2. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,即将发布的Android Studio 3.0版本也会开始内置Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初And ...

  3. Kotlin入门(20)几种常见的对话框

    提醒对话框 手机上的App极大地方便了人们的生活,很多业务只需用户拇指一点即可轻松办理,然而这也带来了一定的风险,因为有时候用户并非真的想这么做,只是不小心点了一下而已,如果App不做任何提示的话,继 ...

  4. Kotlin入门(18)利用单例对象获取时间

    前面介绍了,使用扩展函数可以很方便地扩充数组Array的处理功能,例如交换两个数组元素.求数组的最大元素等等.那么除了数组之外,日期和时间的相关操作,也是很常见的,比如获取当前日期,获取当前时间.获取 ...

  5. Kotlin入门(16)容器的遍历方式

    Kotlin号称全面兼容Java,于是乎Java的容器类仍可在Kotlin中正常使用,包括大家熟悉的队列ArrayList.映射HashMap等等.不过Kotlin作为一门全新的语言,肯定还是要有自己 ...

  6. Kotlin入门(15)独门秘笈之特殊类

    上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...

  7. Kotlin入门(13)类成员的众生相

    上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的.合理的做法是外部访问类的成员属性或者成员方法,从而获得处 ...

  8. Kotlin入门(9)函数的基本用法

    上一篇文章介绍了Kotlin新增的空安全机制,控制语句部分可算是讲完了,接下来将连续描述Kotlin如何定义和调用函数,本篇文章先介绍函数的基本用法. 前面几篇文章介绍控制语句之时,在setOnCli ...

  9. Kotlin入门(7)循环语句的操作

    上一篇文章介绍了简单分支与多路分支的实现,控制语句除了这两种条件分支之外,还有对循环处理的控制,那么本文接下来继续阐述Kotlin如何对循环语句进行操作. Koltin处理循环语句依旧采纳了for和w ...

最新文章

  1. java时间有几种格式_java解析多种时间格式
  2. VTK:八叉树时序用法实战
  3. IReport报表分组与分组统计
  4. 匹配行linux中grep命令的使用
  5. HTML 元素和属性--备份
  6. 一步一步学Silverlight 2系列(5):实现简单的拖放功能_转载
  7. docker安装jdk8
  8. 在IIS7运行ASP程序
  9. android关联发送程序,Android Handler机制之Message的发送与取出
  10. 【AE】缺少Color属性或方法解决办法
  11. 从快感到成就感:多巴胺vs内啡肽
  12. 路由器和交换机的转发过程
  13. 指付通盗刷信用卡维权连载--9月2日维权纪实
  14. 《可复制的领导力》脑图
  15. mysql distance()_mysql距离函数st_distance
  16. 树莓派CM4六路串口设置及使用
  17. 浅谈web前端常用的三大主流框架
  18. 属性动画+贝塞尔曲线实现落叶效果~~~(@_@;)
  19. 经济法基础——第六章第三节综合、与增值税联系较为密切的税种
  20. Foxit Reader(福昕PDF阅读器) v4.3.1.218 绿色专业版

热门文章

  1. Leetcode每日一题:143.reorder-list(重排列表)
  2. 如何解决ValueError: unknown is not supported
  3. poj 3279 poj 1753
  4. 利用栈来处理大数加法
  5. 蓝桥杯 基础练习 字符串对比
  6. SPP-net学习总结及Python实现
  7. 性能测试:基础(4)
  8. python中协程与函数的区别_python协程和异步IO
  9. html track标签,HTML track标签
  10. 遍历所有点的最短路径matlab_运筹学实验8 最短路的求解