列表视图

为实现各种排列组合类的视图(包括但不限于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;
} @Override
public int getCount() {
return mPlanetList.size();
} @Override
public Object getItem(int arg0) {
return mPlanetList.get(arg0);
} @Override
public long getItemId(int arg0) {
return arg0;
} @Override
public 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 = convertView
var 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 LinearLayout
holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView
holder.tv_name = view.findViewById(R.id.tv_name) as TextView
holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView
view.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.name
holder.tv_desc!!.text = planet.desc
return view!!
} inner class ViewHolder {
var ll_item: LinearLayout? = null
var iv_icon: ImageView? = null
var tv_name: TextView? = null
var 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.size

    override 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 = convertView
val holder: ViewHolder
if (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 LinearLayout
holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView
holder.tv_name = view.findViewById(R.id.tv_name) as TextView
holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView
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.name
holder.tv_desc.text = planet.desc
return view!!
} //ViewHolder中的属性使用关键字lateinit延迟初始化
inner class ViewHolder {
lateinit var ll_item: LinearLayout
lateinit var iv_icon: ImageView
lateinit var tv_name: TextView
lateinit 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.size

    override 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 = convertView
val holder: ViewHolder
if (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.name
holder.tv_desc.text = planet.desc
return view!!
} //ViewHolder中的属性在构造时初始化
inner class ViewHolder(val view: View) {
val ll_item: LinearLayout = view.findViewById(R.id.ll_item) as LinearLayout
val iv_icon: ImageView = view.findViewById(R.id.iv_icon) as ImageView
val tv_name: TextView = view.findViewById(R.id.tv_name) as TextView
val tv_desc: TextView = view.findViewById(R.id.tv_desc) as TextView
}
}

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

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

Kotlin入门(22)适配器的简单优化的更多相关文章

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

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

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

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

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

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

  4. 写给Android开发者的Kotlin入门

    写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...

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

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

  6. Kotlin入门(32)网络接口访问

    手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...

  7. Kotlin入门(28)Application单例化

    Application是Android的又一大组件,在App运行过程中,有且仅有一个Application对象贯穿应用的整个生命周期,所以适合在Application中保存应用运行时的全局变量.而开展 ...

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

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

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

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

随机推荐

  1. 《你不知道的JavaScript(中卷)》读书笔记

    中卷有点无聊,不过也是有干货的,但是好像要背一下的样子.不过作者大大都夸我是“优秀的开发人员”了,肯定要看完呀~~ 开发人员觉得它们太晦涩,很难掌握和运用,弊(导致bug)大于利(提高代码可读性).这 ...

  2. 国内使用google搜索引擎

    百度搜索 "谷歌访问助手",点击第一个搜索结果,如下: 或者直接点击链接:http://www.ggfwzs.com/  ,然后点击相应的浏览器下载谷歌访问助手,解压,将解压好的谷 ...

  3. android WebView详解,常见漏洞详解和安全源码

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  转载请注明出处:http://blog.csdn.net/se ...

  4. [工具向]__申请,下载,使用百度地图api

    前言 api即应用程序接口,在我们的日常开发中,我们不仅可以在开源代码仓库(,github,码云)中获得很大的帮助,在日常开发中占重要地位的另一个东西就是api,我们可以使用众多的第三方编写的优秀的a ...

  5. two's complement,2的补码

    本文为作者原创,允许转载,但必须注明原文地址:https://www.cnblogs.com/byronxie/p/10117265.html Let's start with one questio ...

  6. 呕心沥血之作,最多坑mysql5.7安装教程

    前言: 业务需要,需要数据库接binlog发数据变更消息,但是项目用到的数据库是mysql5.6,不支持,于是就有了接下来的一切一切,新的测试服务器上安装mysql5.7 安装步骤: 1.官网下载my ...

  7. openssl dhparam(密钥交换)

    openssl系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html openssl dhparam用于生成和管理dh文件.dh(Diffie-H ...

  8. 特征脸是怎么提取的之主成分分析法PCA

    机器学习笔记 多项式回归这一篇中,我们讲到了如何构造新的特征,相当于对样本数据进行升维. 那么相应的,我们肯定有数据的降维.那么现在思考两个问题 为什么需要降维 为什么可以降维 第一个问题很好理解,假 ...

  9. Visual Studio 2017 - Windows应用程序打包成exe文件(1)- 工具简单总结

    最近有对一个Windows应用程序少许维护和修改.修改之后要发布新的exe安装文件,打包exe文件时,遇到了很头疼的问题,还好最后解决了,记录一下. Visual Studio版本:Visual St ...

  10. .Net C#域账号登陆验证

    //获取登陆页输入的域账号.密码 string domainIP = ""; //域IP string userAccount = ""; //域账号 stri ...