八、Android-UI控件ListView

131 阅读3分钟

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

先创建一个ListViewItem的布局,创建一个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来为这个子项加载我们传入的布局。

LayoutInflaterinflate()方法接收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,用于对ImageViewTextView的控件实例进行缓存,Kotlin中使用inner class关键字来定义内部类。当convertViewnull的时候,创建一个ViewHolder对象,并将控件的实例存放在ViewHolder里,然后调用ViewsetTag()方法,将ViewHolder对象存储在View中。当convertView不为null的时候,则调用ViewgetTag()方法,把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表达式中。