如何更提升ListView的运行效率?

新手上路,请多包涵

问题来自:郭霖的《第一行代码》第三版

在第4.5节中关于提升ListView的内容中

原代码:

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
    val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
    val fruitName:TextView = view.findViewById(R.id.fruitName)
    val fruit = getItem(position)
    if(fruit != null){
        fruitImage.setImageResource(fruit.imageId)
        fruitName.setText(fruit.name)
    }
    return view
}

书中提出:

- converView为缓存的布局,可以避免多次创建View和findView。
- 在View上使用tag附加数据,可以避免多次使用findViewId()

书中是这样建议的:

inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView)

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    val viewHolder:ViewHolder
    val view:View
    if(convertView == null){
        view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
        viewHolder = ViewHolder(fruitImage,fruitName)
        view.tag = viewHolder
    }else{
        view = convertView
        viewHolder = view.tag as ViewHolder
    }
    val fruit = getItem(position)
    if(fruit != null){
        viewHolder.fruitImage.setImageResource(fruit.imageId)
        viewHolder.fruitName.setText(fruit.name)
    }
    return view
}

我的问题是:
书中,把convertView转成view,又从view中的tag读取出fruitImage和fruitName。
那为什么不直接返回converView就行了,它里面的缓存的fruitImage和fruitName也不会变啊。

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    return if(convertView == null){
        val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
        val fruit = getItem(position)
        if(fruit != null){
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        view
    }else{
        convertView
    }
}

这样运行起来也没差别,所以我不懂

阅读 2.2k
3 个回答

只是一个写代码的习惯而已,你直接用convertView也没问题,只是你

val fruit = getItem(position)
        if(fruit != null){
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }

必须放外面,即使有缓存也得执行一遍

使用ListView通常都要考虑用到View重用机制,并结合使用ViewHolder提高性能,以减少findViewById(Int)的调用

ListView本身是支持视图重用机制的,getView(Int, View?, ViewGroup)返回非空对象重用机制就起作用了,由于这个机制存在,就能保证加载显示数据量特别大的列表不致于出现OOM,重用机制能保证子视图数量要明显少于数据项数量的。

参考资料:简书:ListView 布局复用原理分析

另外题主最后一段代码是有问题的,列表在滚动的时候,子视图是不会更新数据的。正确写法如 @白开水 所示。

p.s.RecyclerView内置了上述特性,使用更简单更规范。

新手上路,请多包涵

因为convertview是会复用的,书中的最终目的都是拿到一个创建好的view进行数据更新操作

 return if(convertView == null){
        val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
        val fruit = getItem(position)
        if(fruit != null){
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        view
    }else{
        convertView
    }

你这么写,请问convertview不为空的时候,fruitName能赋值吗。一般来说如果一个对象通过不同方式来创建我们的写法都是下面这样,很常规的写法

var user:User =null
if(xx){
   user = User(xx)
}else{
  user = User(xx)
}
user.print()
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题