ListView的创建和优化-android-kotlin

听说这是最常用和最难用的控件
本文项目中所用的图片资源均来自于https://www.ituring.com.cn/book/2744

ListView处处可见,比如手机QQ的消息列表,新闻应用的信息流页面……

不过相对于Button,TextView这些简单的控件,ListView的用法就要复杂很多。

创建一个简单的ListView

先创建布局文件,这一步和其他控件一样

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

宽度和高度都设置为match_parent,从而使ListView占满整个布局空间

接下来就需要将我们的数据列表有序的放在ListView上面,使数据与ListView建立联系

class MainActivity : AppCompatActivity() {
    //创建数据源
    private val data = listOf<String>("A","B","C","D","E",
    "F","G","H","I","J","K","L","M","N")

    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,从而建立ListView和数据之间的联系
        my_listView.adapter = adapter

    }
}

由于集合中的数据无法直接传递给ListView,我们还需要借助适配器(如ArrayAdapter)来实现
ArrayAdapter通过泛型来指定要适配的数据类型,在这里我们指定了String类型
ArrayAdapter的详细解释:https://www.jianshu.com/p/b996d4af54b0

运行程序,就能看到如下效果

自定义ListView的界面

上述简单的例子只能显示单调的文字,但是我们常用的手机QQ的列表前还配有头像等其他的元素,要实现这样的效果,我们就得自己来定制一个ListView的界面。

首先,列表中每一个单独的子项都看作一个类,我们先定义这个类

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

之后再为我们的每一个子项整一个布局,在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>

ListView与数据连接需要一个适配器,在上面那个示例我们使用了ArrayAdapter<String>作为其适配器。但是我们现在的数据是一个Fruit类,而不是简简单单的String。因此,我们需要创建一个自定义的适配器,继承于ArrayAdapter,并将泛型指定为Fruit类型

class FruitAdapter(activity:Activity,val resourceId:Int,data:List<Fruit>):
        ArrayAdapter<Fruit>(activity,resourceId,data){
    //重写getView方法,每当子项被滚入屏幕时调用此方法来构建子项
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //为子项加载布局
        val view = LayoutInflater.from(context).inflate(resourceId,parent,false)
        //分别获取布局中ImageView和TextView的实例
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
        //获取当前项在ArrayList<Fruit>集合中Fruit实例
        val fruit = getItem(position)
        //设置图片和文字
        if(fruit != null){
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        //最后将布局返回
        return view
    }
}

最后再在MainActivity中使用构建好的这几个类,并为ListView添加监听

class MainActivity : AppCompatActivity() {
    //创建数据源
    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //初始化数据列表
        initfruit()
        //创建适配器
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)
        //将适配器对象传入ListView,从而建立ListView和数据之间的联系
        my_listView.adapter = adapter
        //使用setOnItemClickListener()方法为每一项创建监听
        my_listView.setOnItemClickListener{parent,view,position,id->
            val fruit = fruitList[position]
            Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
        }
    }
    private fun initfruit(){
        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))
        }
    }
}

运行程序,就能看到如下效果

提高ListView的运行效率

由于每次调用getView()方法时都需要使用LayoutInflater重新加载布局。

所以当用户快速滑动的时候,会频繁调用getView()方法反复加载同一个布局,使效率低下。

//重写getView方法,每当子项被滚入屏幕时调用此方法来构建子项
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    //会反复调用LayoutInflater来加载布局
    val view = LayoutInflater.from(context).inflate(resourceId,parent,false)
    //······略·······
}

要解决这个问题,就要使用getView()方法中的一个参数convertView

这个参数用于将之前加载好的布局进行缓存,以便以后进行使用。

我们利用convertView参数来优化上述代码:

//重写getView方法,每当子项被滚入屏幕时调用此方法来构建子项
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    //创建一个
    val view:View
    //第一次加载,则使用LayoutInflater来加载布局,同时convertView会自动缓存这个布局
    if(convertView==null){
        view = LayoutInflater.from(context).inflate(resourceId,parent,false)
    }else{
        //convertView不为空,则说明存有缓存好的布局,直接调用即可
        view = 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 fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName:TextView = view.findViewById(R.id.fruitName)
        //······略······
    }
}

不难发现,重复被调用还有findViewById()方法,它会反复获取相同控件的实例

我们也用缓存的方式来解决这个问题

FruitAdapter中创建一个用来缓存控件实例的内部类ViewHolder

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

使用这个类来缓存实例,从而提高运行效率

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的setTag()方法将ViewHolder对象存储在View中
            view.tag = viewHolder
        }else{//如果已经缓存过了
            //复用缓存的布局
            view = convertView
            //调用getTag()方法将ViewHolder实例取出
            viewHolder = view.tag as ViewHolder
        }
        val fruit = getItem(position)
        if(fruit!=null){
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }
}

猜你喜欢

转载自blog.csdn.net/qq_15989473/article/details/107395911