Android开发基础——ListView

由于手机屏幕空间有有限,能够一次性在屏幕上显示的内容并不多,而程序中有大量的数据需要展示的时候,就需要借助ListView来实现。

简单用法

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

上面的代码中创建了一个ListView,然后使之占满整个布局空间。

package com.example.listviewtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private val data = listOf("Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry",
    "Cherry","Mango","Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry", "Cherry","Mango")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,data)
        listView.adapter = adapter
    }
}

上面的代码中提供了一个字符串列表,然后通过适配器ArrayAdapter指定了适配的数据类型,然后在构造函数中依此传入Activity的实例,ListView子项布局的id,以及数据源。这里使用android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,其中只有一个TextView,可用于简单地显示一段文本。

最后调用ListView的setAdapter方法,传入构建好的适配器对象。程序运行结果为:

 定制ListView的界面

只能显示文本的ListView显然不能满足现在的需求,这里对该界面进行定制。

定义一个实体类Fruit,作为ListView适配器的适配类型:

class Fruit(val name:String, val imageId:Int)

该类只有两个字段,name表示水果名,imageId表示对应的图片资源ID。

然后为ListView子项指定自定义布局,在layout目录下新建fruit_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>

    <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>

在该布局中,定义了ImageView用于显示图片,定义了TextView用于显示文本,并使之在垂直方向上居中显示。

然后创建适配器,继承自ArrayAdapter,并将泛型指定为Fruit:

package com.example.listviewtest

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView

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)
        val fruit = getItem(position)

        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }
}

FruitAdapter重写了getView方法,该方法在每个子项被滚动到屏幕内的时候会被调用。

在getView方法中,首先使用LayoutInflater来为该子项加载用户传入的布局,其inflate方法接收3个参数,前两个参数之前已经提到了,第三个参数false用表示只让用户在父布局中声明的layout属性生效,但不会为该View添加父布局。因为一旦View有了父布局之后,就不能再添加到ListView中了。

然后调用View的findViewById分别获取ImageView和TextView的实例,然后通过getItem获取当前项的Fruit实例,并分别调用其setImageResource和setText方法设置显示的图片和文字,然后布局,适配器就定义完成了。

修改MainActivity中的代码:

package com.example.listviewtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
        listView.adapter = adapter
    }

    private fun initFruits() {
        repeat(2) {
            fruitList.add(Fruit("Apple", R.drawable.apple_pic))
            fruitList.add(Fruit("Banana", R.drawable.banana_pic))
            fruitList.add(Fruit("Orange", R.drawable.orange_pic))
            fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
            fruitList.add(Fruit("Pear", R.drawable.pear_pic))
            fruitList.add(Fruit("Grape", R.drawable.grape_pic))
            fruitList.add(Fruit("Pineapple", R.drawable.pineapple_pic))
            fruitList.add(Fruit("Strawberry", R.drawable.strawberry_pic))
            fruitList.add(Fruit("Cherry", R.drawable.cherry_pic))
            fruitList.add(Fruit("Mango", R.drawable.mango_pic))
        }
    }
}

上面代码中添加了initFruits方法,用于初始化所有的数据。在Fruit类的构造函数中将水果的名字和对应的图片Id传入,然后将创建好的对象添加到列表。另外使用repeat函数对Lambda表达式内容执行多次。最后在onCreate方法中创建了FruitAdapter对象,并将之作为适配器传递到ListView。程序运行后的结果为:

 提升ListView的运行效率

上面的ListView运行效率是很低的,因为在FruitAdapter的getView方法中,每次都会将布局加载一遍,当ListView快速滚动的时候,就会导致性能低下。

其实,在getView方法中还有一个convertView参数,该参数用于将之前加载好的布局进行缓存,以便之后进行重用,这里可以使用该参数进行性能优化:

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 = convertView ?: 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
        }
        return view
    }
}

上面代码中,在getView方法中进行判断,重用了convertView,提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。

而虽然现在不会重复加载布局,但是每次还是会使用findViewById方法获取一次控件的实例。这里可以借助ViewHolder来对这部分性能进行优化:

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
        }
        
        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取出,以优化运行效率。

ListView的点击事件

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
        listView.adapter = adapter

        listView.setOnItemClickListener { parent, view, position, id ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name,Toast.LENGTH_SHORT).show()
        }
    }

上面的代码调用ListView的setOnItemClickListener方法注册了监听器,当用户点击了ListView中的某一个子项时,就会回调Lambda表达式。这里通过position参数判断用户点击的是哪一个子项,然后获取到对应的内容,进行打印。程序运行结果为:

 同时由于只用到了position这一个参数,可以将代码修改为:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
        listView.adapter = adapter

        listView.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name,Toast.LENGTH_SHORT).show()
        }
    }

上边用下划线代替没用的参数,但参数位置不能改变。

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/126919985