最常用和最难用的控件 ListView

创建一个ListViewTest项目。

创建一个布局放入Listview控件,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

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

</LinearLayout>

在mainActivity中,代码如下:

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

先创建一组数据 data ,这些数据可以从网上下载,也可以从数据库中读取,应该视具体的应用程序场景而定。但是数据本身不能直接传输到 listView 中去,需要适配器,Android提供了很多适配器的实现类。

可以使用 ArrayAdapter ,它可以通过泛型来指定要适配的数据类型,然后再构造函数中把数据传入。

由于data均为String,指定泛型为String,然后在构造函数中分别传入  Activity 的实例ListView子项布局的id,以及数据源

我们使用了 android.R.layout.simple_list_item_1 作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。结果如下:

 使用这个ListVire子项布局仅显示一段文本,太过单调,不好看,如果想定制ListView的子项布局,能够优化界面显示。

1、定制ListView界面:

首先定义一个实体类 Fruit

作为ListView的适配器类型,之前使用的是String,由于每次要适配的数据类型不可能仅仅是简单的数据类型,一般可能涉及到很多种数据类型的组合,因此需要构建一个实体类,来进行适配。

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

然后为ListView的子项指定一个我们自定义的布局。

在layout目录中构建一个新的 fruit_item.xml,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <ImageView
        android:id="@+id/imageView"
        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="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>

构建成如下这种样式:

 

一个Image,旁边一个TextView。

接下来需要建立一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit。

代码如下:

class FruitAdapter(activity: Activity, private 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 fruitName: TextView = view.findViewById(R.id.fruitName)
        val fruitImage: ImageView = view.findViewById(R.id.imageView)
        val fruit = getItem(position)   //获取当前Fruit实例
        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }

}

因为是继承了ArrayAdapter,FruitAdapter有一个主构造函数,用于将Activity的实例,ListView的子项布局id,数据源传递进来。

重写了 getView() 方法,这个方法使得每个子项在被滚动到屏幕的时候都会被调用。

getView()方法,首先使用 LayoutInflater 来为这个子项加载我们传入的布局LayoutInflater inflate()函数接受三个参数:第一个参数是要加载的布局文件的id,第二个参数是给加载好的布局添加一个父布局,第三个参数是false,表示只让我们在父布局中声明的layout属性生效,但不会为了这个View添加父布局。

通过findViewById()获得ImageView和ImageView 的实例。

通过getItem()方法得到当前的Fruit实例,并分别调用setImageResource() 和 setText()来设置图片和文字。最后将布局返回,这就是我们自定义的适配器。

最后修改MainActivity中的代码如下:

class MainActivity : AppCompatActivity() {

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val listView: ListView = findViewById(R.id.listView)
        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() 方法,用于初始化所有水果数据。

repeat()方法是Kotlin的一个标准函数,它允许传入一个数值n,然后把Lambda中的内容执行n遍。

最后在onCreate()函数中创建FruitAdapter对象,并将它作为适配器传递给ListView,并将R.layout.fruit_item作为它的子项布局,fruitList作为数据源。

只要修改layout中的fruit_item就可以修改内容,定制各种复杂的界面!!!!

2、提升ListView的运行效率

目前的listview效率很低,因为FruitAdaptergetView()方法,每次都将布局重新加载一遍,造成性能浪费。

getView()中有一个  convertView  参数,这个参数用于将之前加载好的布局进行缓存,方便重用,可以用此来提升效率。

class FruitAdapter(activity: Activity, private 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 fruitName: TextView = view.findViewById(R.id.fruitName)
        val fruitImage: ImageView = view.findViewById(R.id.imageView)
        val fruit = getItem(position)   //获取当前Fruit实例
        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }

}

其中用到了 ?: 判空操作符。当convertView 存在等于convertView ,否则重新创建。

此时,虽然已经调用了已加载的布局,但是还可以继续优化,因为每次调用getView都要重新通过findViewById()获取控件实例,我们可以通过一个 ViewHolder 来进行优化,代码如下:

class FruitAdapter(activity: Activity, private 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 = convertView ?: LayoutInflater.from(context).inflate(resourceId, parent, false)
        val view: View
        val viewHolder: ViewHolder

        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, parent, false)
            val fruitName: TextView = view.findViewById(R.id.fruitName)
            val fruitImage: ImageView = view.findViewById(R.id.imageView)
            viewHolder = ViewHolder(fruitImage, fruitName)
            view.tag = viewHolder
        } else {
            view = convertView
            viewHolder = view.tag as ViewHolder
        }
        
        val fruit = getItem(position)   //获取当前Fruit实例
        if (fruit != null) {
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }

}

新增了一个内部类 ViewHolder ,对ImageView 和 TextView 进行缓存。Kotlin中使用inner class关键字来定义内部类。

当convertView 为空时,创建一个viewHolder对象,并将控件实例放到viewHolder对象中,然后调用view的setTag() 方法,将viewHolder对象存储在View中。

不为空时,调用View的getTag()方法,将viewHolder重新取出,这样就不需要每次都通过findViewById()来获取实例了。

3、ListView的点击事件

需要使用ListView的 setOnItemClickListener 函数,四个参数,点进setOnItemClickListener 源码

    public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener listener) {
        throw new RuntimeException("Stub!");
    }

setOnItemClickListener 接收一个 OnItemClickListener 参数,是一个接口,这是一个Java单抽象方法接口,他只有一个待实现方法,否则也不可能作为参数。

OnItemClickListener 的源码:

    public interface OnItemClickListener {
        void onItemClick(AdapterView<?> var1, View var2, int var3, long var4);
    }

该唯一方法接收四个参数,但是我们实际上只用到了var3,也就是position参数,针对这种情况,Kotlin允许使用 下划线来代替。

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

可以用下划线代替,但是他们之间的位置不可改变!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
尝试着写web下的listview控件listview一般有四种显示模式——平铺、图标、列表和详细信息。这些显示模式唯一的共同点就是数据是相同的,显示效果完全不一样。这时候xml的优势就完全体现出来了。 第一步,就是建立一个自定义格式的xml,用来保存listview数据 第二步,xsl可以解析xml生成html,所以就针对listview的每一种显示效果设计了一个对应的xsl。这样前面定义的xml数据和不同的xsl一起就可以显示出不同的效果。 第三步,htc在开发web控件时,非常灵活和功能强大,可以采用客户端脚本如js,可以对控件进行封装,使之有自己的属性、方法和事件等。利用htc封装的listview控件中对外有两个属性CfgXMLSrc(配置文件,设置listview的每一种显示模式对应的xsl文件路径等信息)和View(listview的显示模式),在htc中根据listview的View属性来选择不同的xsl文件和xml数据文件生成html,并输出。 这样就可以通过改变listview控件的view属性来切换listview的不同显示效果。 在线演示 打包下载 以前写换皮肤的控件,都是通过更换css和图片路径来做的(可以看看http://www.stedy.com),局限性很大,例如toolbar,在winxp和win2000下差别很大,只靠通过换css和图片路径无法应付这种情况。通过开发listview的经验,从中悟到了一种更好的开发换皮肤的web控件的模式: 首先将控件的相关数据用xml描述出来,对于每一种Theme(皮肤/主题样式),有一个相关的配置文件,配置文件中记载了该控件所用到的xsl、css、图片路径、htc等信息。在控件相关的htc中,根据Theme属性组合这些。从而可以灵活的应付各种情况。 例如刚才说的toolbar,假如入我们有三种风格:winxp蓝色、winxp银色和windows经典,前面两种基本差不多,只是样式和图片不一样,而后面一种和前面的两种差别比较大。那么我们需要写两个xsl,三个css文件,三个图片文件夹,组合一下就可以生成这三种风格的toolbar了。 这种控件开发模式会慢慢流行起来并在asp.net控件中发挥重要作用的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值