ListView 就是滑动列表。
4.5.1 ListView 的简单用法
我们新建一个项目ListViewTest ,然后在布局文件中加入ListView 控件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
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"
android:entries="@array/items"
/>
</LinearLayout>
这里要说一个属性android:entries ,它可以调用我们在strings.xml 资源文件中定义的 string-array 标签中的数据展示在每个item 都会占用ListView单独的一行。
strings.xml
<resources>
<string name="app_name">ListViewTest</string>
<string-array name="items">
<item>Apple</item>
<item>Banana</item>
<item>Orange</item>
<item>Watermelon</item>
<item>Pear</item>
<item>Grape</item>
<item>Pineapple</item>
<item>Strawberry</item>
<item>Cherry</item>
<item>Mango</item>
<item>Apple</item>
<item>Banana</item>
<item>Orange</item>
<item>Watermelon</item>
<item>Pear</item>
<item>Grape</item>
<item>Pineapple</item>
<item>Strawberry</item>
<item>Cherry</item>
<item>Mango</item>
</string-array>
</resources>
这是静态添加,下面我们在代码中动态添加数据不去使用android:entries 属性,因为ListView 展示的数据通常都是在网络请求过来的。
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)
listView.adapter = ArrayAdapter(this,android.R.layout.simple_list_item_1,data)
}
}
ListView 的数据是由 Adapter 适配器来提供的,包括它内部用来展示控件的界面,都是由适配器根据数据源的多少遍历而出。我们给ListView 设置的一个适配器 ArrayAdapter,对适配器提供了上下文,布局文件,和数据源。
4.5.2 定制ListView 的界面
光是文本内容的ListView 太过单调,我们可以通过自定义Adapter 来实现带有其他组件的子界面ListView而不是只有一个TextView 文本。
首先创建一个 class 用来存储数据,Fruit 类。
package com.example.listviewtest
class Fruit(val name:String,val imageId:Int)
然后我们来创建自定义的子布局文件。
<?xml version="1.0" encoding="utf-8"?>
<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/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>
这样就可以了,一张图片和一个名字。
接下来是重头戏了,创建一个类 来继承 ArrayAdapter 重写它的getView 函数来自定义布局
class FruitAdapter(context: Context, private val resourceId:Int, data:List<Fruit>) : ArrayAdapter<Fruit>(context,resourceId,data) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val viewHolder:ViewHolder
val view:View
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)
fruit?.let {
viewHolder.apply {
fruitName.text = fruit.name
fruitImage.setImageResource(fruit.imageId) }
}
return view
}
inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView)
}
getView 的常规优化写法。只有第一次convertView 为空的时候 进行布局文件的加载,之后通过不断的在view.tag 中进行取得 ViewHolder 中的对象进行赋值,而不是每一次内部调用getView的时候都进行加载布局文件。
然后我们来定义一下MainActivity 中的数据源和实例化Adapter :
首先创建一个 List 数据源
private val fruitList = ArrayList<Fruit>()
然后初始化数据源。
private fun initFruit(): List<Fruit> {
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))
}
return fruitList
}
repeat 标准函数会将Lambda 表达式中的代码执行 n次,n =传入的次数。repeat(n){}
4.5.4 ListView 的点击事件
现在的ListView 我们实现的只是视觉效果,如果每个子项item 不能点击的话,这个控件就没有实际的意义了。接下来就来实现item 点击事件的监听。
只需要在listView 加入了Adapter 后调用:
listView.setOnItemClickListener { _, _, position, _ ->
val fruit = fruitList[position]
Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
}
setOnItemClickListener 就是每个子项的点击监听回调。这里为什么会出现 _ 这种参数呢?Kotlin 允许我们将没有用到的参数使用下划线来替代,因此这种写法也是合法并且更加推荐的!
其实我们还有一种自定义Adapter的方式,就是创建一个类 继承BaseAdapter 这也是最受欢迎的定义Adapter 方式了,完全的自定义,但是基本逻辑都不会变的,我们来看一下。
class MyAdapter(private val context: Context, private val data:List<Fruit>, private val resourceId:Int) : BaseAdapter() {
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 fruitName:TextView=view.findViewById(R.id.fruitName)
val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
viewHolder = ViewHolder(fruitImage, fruitName)
view.tag = viewHolder
}else{
view = convertView
viewHolder = view.tag as ViewHolder
}
val fruit = getItem(position)
fruit?.let {
viewHolder.apply {
fruitImage.setImageResource(fruit.imageId)
fruitName.text=fruit.name
}
}
return view
}
override fun getItem(position: Int): Fruit =data[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getCount(): Int = data.size
inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)
}