听说这是最常用和最难用的控件
本文项目中所用的图片资源均来自于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)
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){ 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) 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) my_listView.adapter = adapter 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
| override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 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
| override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view:View if(convertView==null){ view = LayoutInflater.from(context).inflate(resourceId,parent,false) }else{ 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.tag = viewHolder }else{ view = convertView viewHolder = view.tag as ViewHolder } val fruit = getItem(position) if(fruit!=null){ viewHolder.fruitImage.setImageResource(fruit.imageId) viewHolder.fruitName.text = fruit.name } return view } }
|