Kotlin 一个扩展函数,从此丢掉 ViewHolder

ViewHolder

作为一名 Android 开发者,对 ViewHolder 应该再熟悉不过了。ViewHolder 一开始并不是 Android 原生提供的(现在已经是 RecycleView 的默认实现了),而是 Google 为了提高 ListView 的使用性能,为开发者提供的一种最佳实践,具体可以参考 ViewHolder。

Google 提供的 ViewHolder 的标准实现如下,熟悉者可以直接跳到下个部分「ViewHolder变种」继续阅读。

static class ViewHolder {
  TextView text;
  TextView timestamp;
  ImageView icon;
  ProgressBar progress;
}

在 Item 第一次创建视图的时候,填充 ViewHolder 并且将其保存在视图中。

ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);

在填充 Item 数据的时候,直接使用 Viewholder 对象的属性,这样可以减少在滚动 ListView 频繁调用 findViewById() 而导致的性能问题。

ViewHolder变种

Google 提供的 ViewHolder 的确能够提升 ListView 的使用效率,但是 ViewHolder 的实现相对繁琐,需要为每一种 Item 定义一个 ViewHolder,对代码书写和维护都是额外的开销。于是有人针对 ViewHolder 的实现做了一些优化,让 ViewHolder 写起来更方便。网上有很多种写法,我最认可的是下面的这种实现,简单优雅。

public class ViewHolder {    
    @SuppressWarnings("unchecked")  
    public static  T get(View view, int id) {  
        SparseArray viewHolder = (SparseArray) view.getTag();  
        if (viewHolder == null) {  
            viewHolder = new SparseArray();  
            view.setTag(viewHolder);  
        }  
        View childView = viewHolder.get(id);  
        if (childView == null) {  
            childView = view.findViewById(id);  
            viewHolder.put(id, childView);  
        }  
        return (T) childView;  
    }  
}  

这里使用 SparseArray 映射每个子视图 id 和对应的子视图,并将其保存在视图中,这样既保证在滚动过程中频繁获取视图的效率,使用起来也极其方便。

ImageView bananaView = ViewHolder.get(convertView, R.id.banana);  
TextView phoneView = ViewHolder.get(convertView, R.id.phone);  
BananaPhone bananaPhone = getItem(position);  
phoneView.setText(bananaPhone.getPhone());

Kotlin 扩展函数

这里Kotlin 实现 ViewHolder 的扩展函数和上面的变种使用的同一种思路,但得益于 Kotlin 语言提供的特性,实现和使用起来更加方便流畅,甚至都感觉不到 ViewHolder 这种特殊机制的存在。

fun  View.findViewOften(viewId: Int): T {
    var viewHolder: SparseArray = tag as? SparseArray ?: SparseArray()
    tag = viewHolder
    var childView: View? = viewHolder.get(viewId)
    if (null == childView) {
        childView = findViewById(viewId)
        viewHolder.put(viewId, childView)
    }
    return childView as T
}

这里实现了一个 View 的扩展函数 findViewOften(viewId: Int) 意味着在需要频繁寻找一个视图的子视图的情况下使用,这样我们在 Item 中就可以这样写了。

val subTitle: TextView = convertView.findViewOften(R.id.list_item_subtitle)
subTitle.text = itemData.subTitle

由于 Kotlin 提供类型推断功能,所以 findViewOften 的返回值不用手动转换或者手动指定泛型类型。

利用 Kotlin 的语言特性,为 View 扩展一个方法,从此再也不用繁琐的定义 Viewholder 了,使用的时候也是如此的顺畅,从此再也不必记得什么 ViewHolder 了。

PS: 该方法在 AndroidExtension 已经提供封装,这个库里面还封装了一些其他方法,也蛮好用的,不过这个库还没有正式发布。

RecycleView 的 ViewHolder

最后,不得不提一下在 RecycleView 应该怎么办,因为在 RecycleView 的机制里面,在创建 Item 的 View 的时候,必须创建一个 RecyclerView.ViewHolder 并且返回。对于我们上面那么完美的封装, Google 这明显是在帮倒忙,还好这忙虽然帮倒了,不过还不至于无法挽回。

如果大家在使用 RecycleView 还想使用本文提供的方法的话,可以参考我下面的方式实现。提供一个 RecyclerView.ViewHolder 默认实现类,该类提供一个通过 id 获取视图的方法,在创建 Item 的 View 的时候默认都返回这个类的实例。

class MyViewHolder(val convertView: View) : RecyclerView.ViewHolder(convertView) {
        fun  findView(viewId: Int): T {
        return convertView.findViewOften(viewId)
    }
}   

如果不想 MyViewHolder 的外部有不需要的依赖,可以将 findViewOften 直接实现在 MyViewHolder 里面。

原文链接:Kotlin 一个扩展函数,从此丢掉 ViewHolder

欢迎大家关注 Kotlin Three,或者关注我们的公众号

参考资料

  • ViewHolder
  • Java Code Examples for android.util.SparseArray
  • AndroidExtension

你可能感兴趣的:(Kotlin 一个扩展函数,从此丢掉 ViewHolder)