Android 7.22-2——7.27 控件ListView

1.ListView的简单用法

ListView控件应用于有大量数据需要展示的时候,允许用户通过手指上下滑动将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。

新建ListViewTest项目,修改布局代码

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

MainActivity中代码

    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
    }

集合中的数据无法直接传递给ListView,所以需要一个适配器,ArrayAdapter是Android提供的一个适配器实现类,根据泛型指定数据的类型,然后在ArrayAdapter的构造函数中以此传入Activity的实例、ListView子项布局的id、数据。android.R.layout.simple_list_item_1作为子项布局的id,它是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。
最后,调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,就完成了ListView和数据之间的关联。

运行结果:
在这里插入图片描述

2.定制ListView界面

准备好图片资源:
在这里插入图片描述
放进res/drawable-xxhdpi/目录下

紧接着,定义一个实体类,作为ListView适配器的适配类型。
新建Fruit类:

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

因为我们需要将图片和名称一起显示在一行,所以这两个可以组成一个布局,就需要为ListView的子项指定一个自定义的布局,新建布局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。
新建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 {
        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
    }
}

FruitAdapter定义了一个主构造函数,将Activity实例、ListView子项布局的id和数据传递进来;并重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
getView()方法中,首先使用LayoutInflater来为这个子项加载我们传入的布局,前两个参数的意义已经明确,false是指只让父布局中声明的layout属性生效,但不会为这个View添加父布局
接下来调用View的findViewById()方法分别获取ImageView和TextView的实例,然后通过getItem()方法得到当前项的Fruit实例,然后set数据即可。
此外,kotlin-android-extensions插件在ListView的适配器中也能正常工作,所以view.findViewById()可以换成view.fruitImage

    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)  // 获取当前项的Fruit实例
        if (fruit != null){
            view.fruitImage.setImageResource(fruit.imageId)
            view.fruitName.text = fruit.name
        }
        return view
    }

然后修改MainActivity中的代码

    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)
//        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
        listView.adapter = adapter
    }

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

可以看到,这里添加了一个initFruits()方法,用于初始化所有的水果数据。在Fruit类的构造函数中将水果名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。另外,使用repeat标准函数将Lamnda表达式的内容执行了两遍,即所有水果添加了两次;再在onCreate()方法中创建了FruitAdapter对象,并将它作为适配器传递给ListView。

运行结果
在这里插入图片描述

3.ListView的点击事件

接下来实现一个实际功能额,因为如果ListView只能看不能点击的话就没有什么用途了。
修改MainActivity中的代码

    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)
//        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
        listView.adapter = adapter
        listView.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
        }
    }
    private fun initFruits(){
        repeat(2){
            fruitList.apply {
                add(Fruit("Apple", R.drawable.apple_pic))
                add(Fruit("Banana", R.drawable.banana_pic))
                add(Fruit("Orange", R.drawable.orange_pic))
                add(Fruit("Watermelon", R.drawable.watermelon_pic))
                add(Fruit("Pear", R.drawable.pear_pic))
                add(Fruit("Grape", R.drawable.grape_pic))
                add(Fruit("Pineapple", R.drawable.pineapple_pic))
                add(Fruit("Strawberry", R.drawable.strawberry_pic))
                add(Fruit("Cherry", R.drawable.cherry_pic))
                add(Fruit("Mango", R.drawable.mango_pic))
            }
        }
    }

这里使用的是setOnItemClickListener()方法注册ListView中的监听器,用于响应每一个子项Item的点击事件,position是判断子项位置的索引参数,其余三个参数没有用到,Kotlin允许我们用下划线代替没有用到的参数。

运行结果
在这里插入图片描述

4.ListView中按钮的点击事件

书上对于ListView的内容到此就未深入讲解了,毕竟作为一本新手友好的入门类书籍,不可能把知识讲解得太过深入。但是,导师给我提出了一个问题,这个问题卡了我一天。。。
他让我在子项布局里加入一个button,用于对不同的item做出不同的响应,即点击苹果的button和点击梨子的button效果是不一样的。
我自己当时写了一个版本,但是并不能识别到当前点击的按钮,按钮为空,即空指针。
此解法为网上看到的版本,原版是Java的,我用AS翻译成了Kotlin,水平不够,自己翻译出了很多问题。
我们知道,适配器Adapter中需要加入子项布局的id,并且我们自己写了fruit_item.xml作为子项布局,因此,想要在每个item中加入button,首先就需要在fruit_item.xml中写一个button,这个应该不难理解。
加在TextView之后:

    <Button
        android:id="@+id/fruitButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="add"
        android:focusable="false"
        android:layout_marginTop="5dp"/>

预览结果应该是这样
在这里插入图片描述
图片、TextView、Button。
需要注意的是,在item中加入button后,会造成点击失焦,即点击item中空白处无法响应上一节中的点击事件,因此需要在button中加入android:focusable="false"属性,值为false,这样可以将点击按钮与点击item中空白位置的点击事件区分开。

继续问题的探索,在上一节中,我们是通过setOnItemClickListener()方法中的position参数才能确定当前点击的是哪一个item,可是,Adapter并没有提供setOnItemButtonClickListener这样的方法啊,我们怎么才能知道当前点击的是哪一个item中的按钮呢。
回到自定义适配器FruitAdapter中,加入以下代码:

    interface OnItemAddListener{
        fun onAddClick(position: Int)
    }

    private var onItemAddListener: OnItemAddListener? = null

    fun setOnItemAddClickListener(onItemDeleteListener: OnItemAddListener) {
        this.onItemAddListener = onItemDeleteListener
    }

定义接口,里面声明一个需要传入position索引为参数的方法,并创建接口引用实例,写一个set方法。
然后到MainActivity中,在onCreate()方法中加入以下代码:

        adapter.setOnItemAddClickListener(object : FruitAdapter.OnItemAddListener{
            override fun onAddClick(position: Int) {
                val fruit = fruitList[position]
                Toast.makeText(this@MainActivity, "add ${fruit.name} to shopping cart",Toast.LENGTH_SHORT).show()
            }
        })

这里就是实现点击时的逻辑了,但是我们还没有为button注册点击响应,并且此处的position一定是需要从一个能够知晓当前item位置信息的地方传入的,回想一下,在第二节中,我们自定义适配器时,重写了一个getView()方法,里面第一个参数就是position位置信息,我们还通过position获取过当前项的Fruit实例。
修改代码:

    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)  // 获取当前项的Fruit实例
        if (fruit != null){
            view.fruitImage.setImageResource(fruit.imageId)
            view.fruitName.text = fruit.name
            //将当前索引跟button绑定
//            view.fruitButton.tag = position
            view.fruitButton.setOnClickListener {
                onItemAddListener!!.onAddClick(position)
            }
        }
        return view
    }

可以看到,我们就做了一件事,给fruitButton注册点击事件,调用接口的方法,并传入position。在MainActivity的页面加载以后,对每一个Item,都会执行一次getView()方法,也就注册了点击事件,当我们点击某个Item中的按钮时,会调用点击事件响应代码:
onItemAddListener!!.onAddClick(position)
此时,onItemAddListener指向了MainActivity中的匿名内部类的实例(上面有此段代码):

        adapter.setOnItemAddClickListener(object : FruitAdapter.OnItemAddListener{
            override fun onAddClick(position: Int) {
                val fruit = fruitList[position]
                Toast.makeText(this@MainActivity, "add ${fruit.name} to shopping cart",Toast.LENGTH_SHORT).show()
            }
        })

就会将position传入重写的onAddClick()方法,进行Toast信息返回。
运行结果:
点击葡萄空白处:
在这里插入图片描述
点击葡萄按钮
在这里插入图片描述
第四节的内容是根据目前个人理解所书写,且为随学笔记,内容不敢保证没有错误,希望路过大佬能够不吝指教,纠正指出其中错误,让我及时修正错误认知的同时也能使内容对观者而言更加可靠,感激不尽!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值