安卓开发学习笔记_UI开发_最常用和最难用的控件:ListView

ListView的简单用法

activity_main.xml中的代码, 如下所示:

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

将宽度和高度都设置为match_parent,这样ListView就占满了整个布局的空间.

MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    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)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 集合中的数据无法直接传递给ListView, 需要借助适配器.
        // 依次传入了Activity的实例, ListView子项布局的id, 以及数据源
        // android.R.layout.simple_list_item_1是android内置的布局文件, 里面只有一个TextView
        val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
        binding.listView.adapter = adapter
    }
}

定制ListView的界面

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

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

自定义布局fruit_item.xml, 如下:

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

自定义适配器FruitAdapter, 如下:

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 {

        // 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
        // 标准写法, 以后再说
        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) // 获取当前项的Fruit实例
        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }
}

修改MainActivity, 如下:

class MainActivity_pic : AppCompatActivity() {

    private lateinit var binding: ActivityMainPicBinding

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainPicBinding.inflate(layoutInflater)
        setContentView(binding.root)


        initFruits() // 初始化水果数据
        val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
        binding.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))
        }
    }
}

提升ListView的运行效率

重用布局

getView()方法中有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用.
修改FruitAdapter代码如下:

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 {

        var view: View
        // convertView参数用于将之前加载好的布局进行缓存
        if(convertView == null) { // 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
            // 标准写法, 以后再说
            view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        } else {
            view = convertView
        }
        ...
    }
}

重用控件

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
        // convertView参数用于将之前加载好的布局进行缓存
        if (convertView == null) {
            // 当convertView为null的时候, 创建一个ViewHolder对象, 并将控件的实例放在ViewHolder里, 然后调用->
            // ->View的setTag()方法, 将ViewHolder对象存储在View中.

            // 标准写法: 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
            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 {
            // 当convertView不为null的时候, 则调用View的getTag()方法, 把ViewHolder重新取出.

            view = convertView
            viewHolder = view.tag as ViewHolder
        }

        // 所有控件的实例都缓存在了ViewHolder里

        val fruit = getItem(position) // 获取当前项的Fruit实例
        if (fruit != null) {
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }
}

ListView的点击事件

class MainActivity_pic : AppCompatActivity() {

    private lateinit var binding: ActivityMainPicBinding

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainPicBinding.inflate(layoutInflater)
        setContentView(binding.root)

        initFruits() // 初始化水果数据
        val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
        binding.listView.adapter = adapter

        // setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时->
        // ->就会回调到Lambda表达式中
        // setOnItemClickListener()方法接收一个OnItemClickListener参数, 该参数的待实现方法onItemClick()->
        // ->中接收4个参数, 没有用到的参数推荐用下划线代替(_, _, position, _), 参数的位置不能改变
        binding.listView.setOnItemClickListener { parent, view, position, id ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
        }
    }

    ...
}

参考

郭霖. 《第一行代码 Android 第3版》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_cen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值