列表视图

为实现各种排列组合类的视图(包括但不限于Spinner、ListView、GridView等等),Android提供了五花八门的适配器用于组装某个规格的数据,常见的适配器有:数组适配器ArrayAdapter、简单适配器SimpleAdapter、基本适配器BaseAdapter、翻页适配器PagerAdapter。适配器的种类虽多,却个个都不好用,以数组适配器为例,它与Spinner配合实现下拉框效果,其实现代码纷复繁杂,一直为人所诟病。故而在下拉框一小节之中,干脆把ArrayAdapter连同Spinner一股脑都摒弃了,取而代之的是Kotlin扩展函数selector。
到了列表视图ListView这里,与之搭档的一般是基本适配器BaseAdapter,这个BaseAdapter更不简单,基于它的列表适配器得重写好几个方法,还有那个想让初学者撞墙的ViewHolder。总之,每当要实现类似新闻列表、商品列表之类的页面,一想到这个难缠的BaseAdapter,心里便发怵。譬如下图所示的六大行星的说明列表,左侧是图标,右边为文字说明,很普通的一个页面。

可是这个行星列表页面,倘若使用Java编码,就得书写下面一大段长长的代码:

public class PlanetJavaAdapter extends BaseAdapter  {private Context mContext;private ArrayList<Planet> mPlanetList;private int mBackground;public PlanetJavaAdapter(Context context, ArrayList<Planet> planet_list, int background) {mContext = context;mPlanetList = planet_list;mBackground = background;}@Overridepublic int getCount() {return mPlanetList.size();}@Overridepublic Object getItem(int arg0) {return mPlanetList.get(arg0);}@Overridepublic long getItemId(int arg0) {return arg0;}@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null) {holder = new ViewHolder();convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_view, null);holder.ll_item = (LinearLayout) convertView.findViewById(R.id.ll_item);holder.iv_icon = (ImageView) convertView.findViewById(R.id.iv_icon);holder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);holder.tv_desc = (TextView) convertView.findViewById(R.id.tv_desc);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}Planet planet = mPlanetList.get(position);holder.ll_item.setBackgroundColor(mBackground);holder.iv_icon.setImageResource(planet.image);holder.tv_name.setText(planet.name);holder.tv_desc.setText(planet.desc);return convertView;}    public final class ViewHolder {public LinearLayout ll_item;public ImageView iv_icon;public TextView tv_name;public TextView tv_desc;}
}

上面Java实现的适配器类PlanetJavaAdapter,果真又冗长又晦涩,然而这段代码模版基本上是列表视图的标配,只要用Java编码,就必须依样画瓢。如果用Kotlin实现这个适配器类会是怎样的呢?马上利用Android Studio把上述Java代码转换为Kotlin编码,转换后的Kotlin代码类似以下片段:

class PlanetKotlinAdapter(private val mContext: Context, private val mPlanetList: ArrayList<Planet>, private val mBackground: Int) : BaseAdapter() {override fun getCount(): Int {return mPlanetList.size}override fun getItem(arg0: Int): Any {return mPlanetList[arg0]}override fun getItemId(arg0: Int): Long {return arg0.toLong()}override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {var view = convertViewvar holder: ViewHolder?if (view == null) {holder = ViewHolder()view = LayoutInflater.from(mContext).inflate(R.layout.item_list_view, null)holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayoutholder.iv_icon = view.findViewById(R.id.iv_icon) as ImageViewholder.tv_name = view.findViewById(R.id.tv_name) as TextViewholder.tv_desc = view.findViewById(R.id.tv_desc) as TextViewview.tag = holder} else {holder = view.tag as ViewHolder}val planet = mPlanetList[position]holder.ll_item!!.setBackgroundColor(mBackground)holder.iv_icon!!.setImageResource(planet.image)holder.tv_name!!.text = planet.nameholder.tv_desc!!.text = planet.descreturn view!!}    inner class ViewHolder {var ll_item: LinearLayout? = nullvar iv_icon: ImageView? = nullvar tv_name: TextView? = nullvar tv_desc: TextView? = null}
}

相比之下,直接转换得来的Kotlin代码,最大的改进是把构造函数及初始化参数放到了第一行,其它地方未有明显优化。眼瞅着没多大改善,反而因为Kotlin的空安全机制,平白无故多了好些问号和双感叹号,可谓得不偿失。问题出在Kotlin要求每个变量都要初始化上面,视图持有者ViewHolder作为一个内部类,目前虽然无法直接对控件对象赋值,但是从代码逻辑可以看出先从布局文件获取控件,然后才会调用各种设置方法。这意味着,上面的控件对象必定是先获得实例,在它们被使用的时候肯定是非空的,因此完全可以告诉编译器,这些控件对象一定会在使用前赋值,编译器您老就高抬贵手,睁一只眼闭一只眼放行好了。
毋庸置疑,该想法合情合理,Kotlin正好提供了这种后门,它便是关键字lateinit。lateinit的意思是延迟初始化,它放在var或者val前面,表示被修饰的变量属于延迟初始化属性,即使没有初始化也仍然是非空的。如此一来,这些控件在声明之时无需赋空值,在使用的时候也不必画蛇添足加上两个感叹号了。根据新来的lateinit修改前面的Kotlin适配器,改写后的Kotlin代码如下所示:

class PlanetListAdapter(private val context: Context, private val planetList: MutableList<Planet>, private val background: Int) : BaseAdapter() {override fun getCount(): Int = planetList.sizeoverride fun getItem(position: Int): Any = planetList[position]override fun getItemId(position: Int): Long = position.toLong()override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {var view = convertViewval holder: ViewHolderif (convertView == null) {view = LayoutInflater.from(context).inflate(R.layout.item_list_view, null)holder = ViewHolder()//先声明视图持有者的实例,再依次获取内部的各个控件对象holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayoutholder.iv_icon = view.findViewById(R.id.iv_icon) as ImageViewholder.tv_name = view.findViewById(R.id.tv_name) as TextViewholder.tv_desc = view.findViewById(R.id.tv_desc) as TextViewview.tag = holder} else {holder = view.tag as ViewHolder}val planet = planetList[position]holder.ll_item.setBackgroundColor(background)holder.iv_icon.setImageResource(planet.image)holder.tv_name.text = planet.nameholder.tv_desc.text = planet.descreturn view!!}//ViewHolder中的属性使用关键字lateinit延迟初始化inner class ViewHolder {lateinit var ll_item: LinearLayoutlateinit var iv_icon: ImageViewlateinit var tv_name: TextViewlateinit var tv_desc: TextView}
}

以上的Kotlin代码总算有点模样了,虽然总体代码还不够精简,但是至少清晰明了,其中主要运用了Kotlin的以下三项技术:
1、构造函数和初始化参数放在类定义的首行,无需单独构造,也无需手工初始化;
2、像getCount、getItem、getItemId这三个函数,仅仅返回简单运算的数值,可以直接用等号取代大括号;
3、对于视图持有者的内部控件,在变量名称前面添加lateinit,表示该属性为延迟初始化属性;

网格视图

在前面的列表视图一小节中,给出了Kotlin改写后的适配器类,通过关键字lateinit固然避免了麻烦的空校验,可是控件对象迟早要初始化的呀,晚赋值不如早赋值。翻到前面PlanetListAdapter的实现代码,认真观察发现控件对象的获取其实依赖于布局文件的视图对象view,既然如此,不妨把该视图对象作为ViewHolder的构造参数传过去,使得视图持有者在构造之时便能一块初始化内部控件。据此改写后的Kotlin适配器代码如下所示:

class PlanetGridAdapter(private val context: Context, private val planetList: MutableList<Planet>, private val background: Int) : BaseAdapter() {override fun getCount(): Int = planetList.sizeoverride fun getItem(position: Int): Any = planetList[position]override fun getItemId(position: Int): Long = position.toLong()override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {var view = convertViewval holder: ViewHolderif (view == null) {view = LayoutInflater.from(context).inflate(R.layout.item_grid_view, null)holder = ViewHolder(view)//视图持有者的内部控件对象已经在构造时一并初始化了,故这里无需再做赋值view.tag = holder} else {holder = view.tag as ViewHolder}val planet = planetList[position]holder.ll_item.setBackgroundColor(background)holder.iv_icon.setImageResource(planet.image)holder.tv_name.text = planet.nameholder.tv_desc.text = planet.descreturn view!!}//ViewHolder中的属性在构造时初始化inner class ViewHolder(val view: View) {val ll_item: LinearLayout = view.findViewById(R.id.ll_item) as LinearLayoutval iv_icon: ImageView = view.findViewById(R.id.iv_icon) as ImageViewval tv_name: TextView = view.findViewById(R.id.tv_name) as TextViewval tv_desc: TextView = view.findViewById(R.id.tv_desc) as TextView}
}

利用该适配器运行测试应用,得到的网格效果如下图所示,可见与Java代码的运行结果完全一致。

至此基于BaseAdapter的Kotlin列表适配器告一段落,上述的适配器代码模版,同时适用于列表视图ListView与网格视图GridView。

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

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

Kotlin入门(22)适配器的简单优化相关推荐

  1. Kotlin入门(23)适配器的进阶表达

    前面在介绍列表视图和网格视图时,它们的适配器代码都存在视图持有者ViewHolder,因为Android对列表类视图提供了回收机制,如果某些列表项在屏幕上看不到了,则系统会自动回收相应的视图对象.随着 ...

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

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

  3. Kotlin入门(11)江湖绝技之特殊函数

    上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...

  4. kotlin入门学习文档

    kotlin入门学习文档 前言:本文会着重对比java和kotlin,方便Java选手理解 提前总结:kotlin在服务端应用本质上是基于Java进行的改进,底层都是由JVM翻译成底层语言,我们只需要 ...

  5. Kotlin入门(5)字符串及其格式化

    上一篇文章介绍了数组的声明和操作,包括字符串数组的用法.注意到Kotlin的字符串类也叫String,那么String在Java和Kotlin中的用法有哪些差异呢?这便是本文所要阐述的内容了. 首先要 ...

  6. Kotlin入门(33)运用扩展属性

    进行App开发的时候,使用震动器要在AndroidManifest.xml中加上如下权限: <!-- 震动 --><uses-permission android:name=&quo ...

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

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

  8. Kotlin入门(6)条件分支的实现

    上一篇文章介绍了字符串的相关操作,其中示例代码用到了if和for语句,表面上看,Kotlin对控制语句的处理与Java很像,可实际上,Kotlin在这方面做了不少的改进,所以本篇和下一篇文章就分别介绍 ...

  9. 【Kotlin入门教程】史上最全最易于理解最全面的文章

    这里写自定义目录标题 前言 Kotlin学习笔记 1.val和var区别 2.简单变量之间的转换 3.数组变量的声明 4.字符串 字符串与基本类型的转换 字符串常用方法 字符串模板及其拼接 5.容器 ...

最新文章

  1. python matplotlib画数据分布图_Python数据可视化之matplotlib
  2. Java实现文件拷贝
  3. 关于AttributeError: type object ‘XXX‘ has no attribute ‘XXX‘的问题
  4. 异常处理---SpringMVC学习笔记(十)
  5. python中的元组操作
  6. C#深入解析数据类型
  7. 高管访谈:AI 驱动的新兴金融市场改革,还有这三项障碍
  8. oracle不弹出另存为,Oracle另存为~
  9. 安装 PHP memcached 扩展遇到的3个问题
  10. Windows常用运行库合集--官网(VC++、DirectX、.NET)
  11. 自动驾驶领域常见专业英文名词及其含义
  12. 史上最简单的Git入门教程
  13. Excel如何冻结窗口
  14. “铁老大”价格坚冰松动 成都火车票打折三成
  15. brew cask安装软件提示:Error: Unknown command: cask
  16. Synology群晖小技巧之百度网盘远程下载同步
  17. 【微信小游戏】排行榜概念篇
  18. 第十六届全国大学生信息安全竞赛创新实践能力赛(CISCN)
  19. 中软国际 c语言面试题,中软国际笔试题
  20. 硬盘录像机出现系统崩溃?如何恢复?

热门文章

  1. 并行算法第五讲:Pthread编程
  2. Redis基础(六)——事务
  3. 微信小程序之验证码短信倒计时
  4. SpringBoot App Registers UNKNOWN with Eureka in Brixton SR3
  5. LeetCode刷题(31)
  6. 2、Fiddler工作原理
  7. 1、Fiddler的基本介绍
  8. java入门申请,《java入门如此简单》——基础知识1
  9. SpringBoot实战教程(5)| 整合Freemaker
  10. Java之消息摘要之commons codec