听说这是最常用和最难用的控件

本文项目中所用的图片资源均来自于https://www.ituring.com.cn/book/2744

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

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

创建一个简单的ListView

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

1
2
3
4
<ListView
android:id="@+id/my_listView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的界面。

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

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

之后再为我们的每一个子项整一个布局,在layout目录下创建fruit_item.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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添加监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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()方法反复加载同一个布局,使效率低下。

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
//重写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
}
//······略·······
}

我们还可以进一步优化,观察以下代码

1
2
3
4
5
6
7
8
9
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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
}
}

 评论

载入天数...载入时分秒...