8. UI控件-ListView
8.1 ListView的简单使用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listView1"
/>
</LinearLayout>
val data = listOf("Apple", "Banana", "Orange", "Watermelon",
"Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
"Pineapple", "Strawberry", "Cherry", "Mango")
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
binding.listView1.adapter = adapter
android.R.layout.simple_list_item_1作为ListView子项布局的id,以android.R开头的都是AndroidSDK自带的资源。simple_list_item_1里面只有一个简单的TextView文本。
8.2 定制ListView
先创建一个ListView的Item的布局,创建一个fruit_item布局文件。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp"
/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
然后准备数据源和适配器
data class Fruit(val name: String, val imageId: Int) {
}
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>):
ArrayAdapter<Fruit>(activity, resourceId, data) {
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)
// 获取当前fruit实例
val fruit = getItem(position)
if (fruit != null) {
fruitImage.setImageResource(fruit.imageId)
fruitName.text = fruit.name
}
return view
}
}
FruitAdapter定义了一个主构造函数,用于将Activity的实例、ListView子项布局的id和数
据源传递进来。另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会
被调用。
在getView()方法中,首先使用LayoutInflater来为这个子项加载我们传入的布局。
LayoutInflater的inflate()方法接收3个参数,前两个参数我们已经知道是什么意思了,
第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个
View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
然后使用:
val fruits = data.map {
Fruit(it, R.drawable.img_1)
}
val adapter = FruitAdapter(this, R.layout.fruit_item, fruits)
binding.listView1.adapter = adapter
8.3 优化ListView的运行效率
getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用,我们可以借助这个参数来进行性能优化:
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View
if (convertView == null) {
view = LayoutInflater.from(context).inflate(resourceId, parent, false)
} else {
view = convertView
}
val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
// 获取当前fruit实例
val fruit = getItem(position)
if (fruit != null) {
fruitImage.setImageResource(fruit.imageId)
fruitName.text = fruit.name
}
return view
}
继续优化:
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>):
ArrayAdapter<Fruit>(activity, resourceId, data) {
inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View
val viewHolder: ViewHolder
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
}
// 获取当前fruit实例
val fruit = getItem(position)
if (fruit != null) {
viewHolder.fruitImage.setImageResource(fruit.imageId)
viewHolder.fruitName.text = fruit.name
}
return view
}
}
我们新增了一个内部类ViewHolder,用于对ImageView和TextView的控件实例进行缓存,Kotlin中使用inner class关键字来定义内部类。当convertView为null的时候,创建一个ViewHolder对象,并将控件的实例存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null的时候,则调用View的getTag()方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。
这样之后,ListView的运行效率就很高了。
8.4 ListView的点击事件
binding.listView1.setOnItemClickListener { adapterView, view, i, l ->
val fruit = fruits[i]
Toast.makeText(this, fruit.name + l, Toast.LENGTH_SHORT).show()
}
通过setOnItemClickListener 方法注册一个监听器,当用户点击ListView中子项时,回调到Lambda表达式中。