Kotlin入门(22)适配器的简单优化
列表视图
为实现各种排列组合类的视图(包括但不限于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。
上一篇: RxJava2 入门详细笔记