安卓开发学习笔记_UI开发_最常用和最难用的控件:ListView
ListView的简单用法
activity_main.xml
中的代码, 如下所示:
<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>
将宽度和高度都设置为match_parent
,这样ListView
就占满了整个布局的空间.
MainActivity
中的代码,如下所示:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
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)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 集合中的数据无法直接传递给ListView, 需要借助适配器.
// 依次传入了Activity的实例, ListView子项布局的id, 以及数据源
// android.R.layout.simple_list_item_1是android内置的布局文件, 里面只有一个TextView
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
binding.listView.adapter = adapter
}
}
定制ListView的界面
定义一个实体类,作为ListView
适配器的适配类型.
class Fruit(val name: String, val imageId: Int)
自定义布局fruit_item.xml
, 如下:
<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>
自定义适配器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 {
// 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
// 标准写法, 以后再说
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
}
}
修改MainActivity
, 如下:
class MainActivity_pic : AppCompatActivity() {
private lateinit var binding: ActivityMainPicBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainPicBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits() // 初始化水果数据
val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
binding.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))
}
}
}
提升ListView的运行效率
重用布局
getView()
方法中有一个convertView
参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用.
修改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 {
var view: View
// convertView参数用于将之前加载好的布局进行缓存
if(convertView == null) { // 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
// 标准写法, 以后再说
view = LayoutInflater.from(context).inflate(resourceId, parent, false)
} else {
view = convertView
}
...
}
}
重用控件
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
// convertView参数用于将之前加载好的布局进行缓存
if (convertView == null) {
// 当convertView为null的时候, 创建一个ViewHolder对象, 并将控件的实例放在ViewHolder里, 然后调用->
// ->View的setTag()方法, 将ViewHolder对象存储在View中.
// 标准写法: 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。
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 {
// 当convertView不为null的时候, 则调用View的getTag()方法, 把ViewHolder重新取出.
view = convertView
viewHolder = view.tag as ViewHolder
}
// 所有控件的实例都缓存在了ViewHolder里
val fruit = getItem(position) // 获取当前项的Fruit实例
if (fruit != null) {
viewHolder.fruitImage.setImageResource(fruit.imageId)
viewHolder.fruitName.text = fruit.name
}
return view
}
}
ListView的点击事件
class MainActivity_pic : AppCompatActivity() {
private lateinit var binding: ActivityMainPicBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainPicBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits() // 初始化水果数据
val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
binding.listView.adapter = adapter
// setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时->
// ->就会回调到Lambda表达式中
// setOnItemClickListener()方法接收一个OnItemClickListener参数, 该参数的待实现方法onItemClick()->
// ->中接收4个参数, 没有用到的参数推荐用下划线代替(_, _, position, _), 参数的位置不能改变
binding.listView.setOnItemClickListener { parent, view, position, id ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
}
...
}