• Android开发基础——ListView


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

    简单用法

    1. "1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent">
    5. <ListView
    6. android:id="@+id/listView"
    7. android:layout_width="match_parent"
    8. android:layout_height="match_parent"/>
    9. LinearLayout>

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

    1. package com.example.listviewtest
    2. import androidx.appcompat.app.AppCompatActivity
    3. import android.os.Bundle
    4. import android.widget.ArrayAdapter
    5. import kotlinx.android.synthetic.main.activity_main.*
    6. class MainActivity : AppCompatActivity() {
    7. private val data = listOf("Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry",
    8. "Cherry","Mango","Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry", "Cherry","Mango")
    9. override fun onCreate(savedInstanceState: Bundle?) {
    10. super.onCreate(savedInstanceState)
    11. setContentView(R.layout.activity_main)
    12. val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,data)
    13. listView.adapter = adapter
    14. }
    15. }

    上面的代码中提供了一个字符串列表,然后通过适配器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:

    1. "1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="60dp">
    5. <ImageView
    6. android:id="@+id/fruitImage"
    7. android:layout_width="40dp"
    8. android:layout_height="40dp"
    9. android:layout_gravity="center_vertical"
    10. android:layout_marginLeft="10dp"/>
    11. <TextView
    12. android:id="@+id/fruitName"
    13. android:layout_width="wrap_content"
    14. android:layout_height="wrap_content"
    15. android:layout_gravity="center_vertical"
    16. android:layout_marginLeft="10dp"/>
    17. LinearLayout>

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

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

    1. package com.example.listviewtest
    2. import android.app.Activity
    3. import android.view.LayoutInflater
    4. import android.view.View
    5. import android.view.ViewGroup
    6. import android.widget.ArrayAdapter
    7. import android.widget.ImageView
    8. import android.widget.TextView
    9. class FruitAdapter(activity: Activity, val resourceId:Int, data:List):
    10. ArrayAdapter(activity, resourceId, data){
    11. override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    12. val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
    13. val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
    14. val fruitName:TextView = view.findViewById(R.id.fruitName)
    15. val fruit = getItem(position)
    16. if (fruit != null) {
    17. fruitImage.setImageResource(fruit.imageId)
    18. fruitName.text = fruit.name
    19. }
    20. return view
    21. }
    22. }

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

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

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

    修改MainActivity中的代码:

    1. package com.example.listviewtest
    2. import androidx.appcompat.app.AppCompatActivity
    3. import android.os.Bundle
    4. import android.widget.ArrayAdapter
    5. import kotlinx.android.synthetic.main.activity_main.*
    6. class MainActivity : AppCompatActivity() {
    7. private val fruitList = ArrayList()
    8. override fun onCreate(savedInstanceState: Bundle?) {
    9. super.onCreate(savedInstanceState)
    10. setContentView(R.layout.activity_main)
    11. initFruits()
    12. val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
    13. listView.adapter = adapter
    14. }
    15. private fun initFruits() {
    16. repeat(2) {
    17. fruitList.add(Fruit("Apple", R.drawable.apple_pic))
    18. fruitList.add(Fruit("Banana", R.drawable.banana_pic))
    19. fruitList.add(Fruit("Orange", R.drawable.orange_pic))
    20. fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
    21. fruitList.add(Fruit("Pear", R.drawable.pear_pic))
    22. fruitList.add(Fruit("Grape", R.drawable.grape_pic))
    23. fruitList.add(Fruit("Pineapple", R.drawable.pineapple_pic))
    24. fruitList.add(Fruit("Strawberry", R.drawable.strawberry_pic))
    25. fruitList.add(Fruit("Cherry", R.drawable.cherry_pic))
    26. fruitList.add(Fruit("Mango", R.drawable.mango_pic))
    27. }
    28. }
    29. }

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

     提升ListView的运行效率

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

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

    1. class FruitAdapter(activity: Activity, val resourceId:Int, data:List):
    2. ArrayAdapter(activity, resourceId, data){
    3. override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    4. val view = convertView ?: LayoutInflater.from(context).inflate(resourceId, parent, false)
    5. val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
    6. val fruitName:TextView = view.findViewById(R.id.fruitName)
    7. val fruit = getItem(position)
    8. if (fruit != null) {
    9. fruitImage.setImageResource(fruit.imageId)
    10. fruitName.text = fruit.name
    11. }
    12. return view
    13. }
    14. }

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

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

    1. class FruitAdapter(activity: Activity, val resourceId:Int, data:List):
    2. ArrayAdapter(activity, resourceId, data){
    3. inner class ViewHolder(val fruitImage:ImageView, val fruitName:TextView)
    4. override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    5. val view:View
    6. val viewHolder:ViewHolder
    7. if (convertView == null) {
    8. view = LayoutInflater.from(context).inflate(resourceId, parent, false)
    9. val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
    10. val fruitName:TextView = view.findViewById(R.id.fruitName)
    11. viewHolder = ViewHolder(fruitImage, fruitName)
    12. view.tag = viewHolder
    13. } else {
    14. view = convertView
    15. viewHolder = view.tag as ViewHolder
    16. }
    17. val fruit = getItem(position)
    18. if (fruit != null) {
    19. viewHolder.fruitImage.setImageResource(fruit.imageId)
    20. viewHolder.fruitName.text = fruit.name
    21. }
    22. return view
    23. }
    24. }

    上面的代码中,新增了内部类ViewHolder,用于缓存ImageView和TextView的实例。Kotlin使用inner class关键字来构建内部类。而当convertView为null时,就创建一个ViewHolder对象,并将控件实例存放在ViewHolder中,然后调用View的setTag方法,将ViewHolder对象存储在View中。当convertView不为null时,就调用View的getTag方法,将ViewHolder取出,以优化运行效率。

    ListView的点击事件

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. setContentView(R.layout.activity_main)
    4. initFruits()
    5. val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
    6. listView.adapter = adapter
    7. listView.setOnItemClickListener { parent, view, position, id ->
    8. val fruit = fruitList[position]
    9. Toast.makeText(this, fruit.name,Toast.LENGTH_SHORT).show()
    10. }
    11. }

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

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

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. setContentView(R.layout.activity_main)
    4. initFruits()
    5. val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
    6. listView.adapter = adapter
    7. listView.setOnItemClickListener { _, _, position, _ ->
    8. val fruit = fruitList[position]
    9. Toast.makeText(this, fruit.name,Toast.LENGTH_SHORT).show()
    10. }
    11. }

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

  • 相关阅读:
    pymysql连接Mariadb/Mysql出现错误(配置正确情况下)解决办法
    代码随想录训练营第31天|LeetCode 455.分发饼干、 376. 摆动序列、53. 最大子序和
    pytorch应用于MNIST手写字体识别
    Web安全知识
    FPGA时序约束01——基本概念
    GET 和 POST请求的区别是什么
    嵌入式软件学习进阶
    OSPF高级特性——控制OSPF路由信息
    滚珠花键各大品牌简述
    Llama 3 开源了「GitHub 热点速览」
  • 原文地址:https://blog.csdn.net/SAKURASANN/article/details/126919985