组成
构建一般的ListView
包含三部分:
- ListView布局(控件)
- MainActivity(承载Activity或Fragment等)
- Adapter适配器
Adapter
Adapter
是适配器,一个通过泛型来指定要适配的数据类型,并在构造函数中将要适配的数据传入。可以简单理解为数据绑定,并将其显示在ListView
上,可以理解类似于MVP
中的Presenter
。其作为View
和“Model
数据”之间的桥梁,ListView
会在Adapter
中得到需要的数据,并加载出来。而ListView只是一种列表的表现形式,将获取的数据以列表的方式进行展现。
通常我们会选用继承ArrayAdapter
进行数组数据的展示,或者继承BaseAdapter
进行自定义Adapter
。
ArrayAdapter
以简单、便捷的继承ArrayAdapter
展开,快速构建一个ListView实例。
- 根据上述组成的三步,首先是
ListView
布局的添加。我们先构建一个id为listview_test
的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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview_test"/>
</LinearLayout>
- 紧接着是继承
ArrayAdapter
的MyAdapter
适配器构建,首先创建一个User
实体类:
class User(val name:String) {}
再创建MyAdapter
。重写getView
使得每个子项滚动到可视窗口界面时会被调用;getItem
获取当前位置的User
实例
class MyAdapter(mActivity: Activity, val resourceId: Int,val myList:List<User>): ArrayAdapter<User>(mActivity,resourceId,myList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = LayoutInflater.from(context).inflate(resourceId,parent,false)
//绑定布局中user_name
val userName:TextView = view.findViewById(R.id.user_name)
//获取当前位置的user实例
val user= getItem(position)
if(user != null){
userName.text =user.name
}
return view
}
}
- 最后是
MainActivity
,Java中通过setAdapter
往ListView
中传入Adapter
,Kotlin中通过listview.adapter
传入。
class MainActivity : AppCompatActivity() {
private val user = ArrayList<User>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//初始化测试数据
initUser()
val adapter=MyAdapter(this, R.layout.user_list_item_layout,user)
//往listview中传入adapter
listview_test.adapter =adapter
}
private fun initUser(){
for(i in 0..10){
user.add(User("用户test"+i))
}
}
}
针对Item的点击监听示例代码如下:
fun listviewtest(){
val adapter=MyAdapter(this, R.layout.user_list_item_layout,user)
listview_test.adapter =adapter
listview_test.setOnItemClickListener{parent,view,position,id->
var userTest = user[position]
userTest.name = "zhangkai"
adapter.notifyDataSetChanged()
}
}
通过user[position]
获取被点击的Item
,并修改其Item.name
,并通过adapter.notifyDataSetChanged()
通知ListView
发生变化了。(成果图和Demo代码中未包含监听部分)
成果图
性能优化
在上述方法中,getView每次调用都会将布局重新加载一遍,造成效率低。因此使用getView中的convertView参数对之前加载过的布局进行缓存,从而优化性能。
class MyAdapter(mActivity: Activity, val resourceId: Int,val myList:List<User>): ArrayAdapter<User>(mActivity,resourceId,myList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view:View
//使用convertView进行缓存
if( convertView==null){
view = LayoutInflater.from(context).inflate(resourceId,parent,false)
}else{
view = convertView
}
// val view = LayoutInflater.from(context).inflate(resourceId,parent,false)
//绑定布局中user_name
val userName:TextView = view.findViewById(R.id.user_name)
//获取当前位置的user实例
val user= getItem(position)
if(user != null){
userName.text =user.name
}
return view
}
}
当前解决了每次getView
重新加载布局的问题,但是getView
中View
依旧会每次通过findViewById()
获取控件实例,可以引入ViewHolder
进行优化。
class MyAdapter(mActivity: Activity, val resourceId: Int,val myList:List<User>): ArrayAdapter<User>(mActivity,resourceId,myList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view:View
val myViewHolder: MyViewHolder
//使用convertView进行缓存
if( convertView==null){
view = LayoutInflater.from(context).inflate(resourceId,parent,false)
//绑定布局中user_name
val userName:TextView = view.findViewById(R.id.user_name)
myViewHolder = MyViewHolder(userName)
view.tag = myViewHolder
}else{
view = convertView
myViewHolder = view.tag as MyViewHolder
}
//获取当前位置的user实例
val user= getItem(position)
if(user != null){
myViewHolder.userName.text = user.name
}
return view
}
//新增MyViewHolder对控件进行缓存
class MyViewHolder(val userName:TextView)
}
当convertView
为null
时,调用View.setTag()
将控件实例存到myViewHolder
中,convertView
不为null
时通过getTag()
把已有的控件myViewHolder
取出来。(Kotlin语法,get\set
可以直接view.tag
代替)
Demo代码
GitHub:alicechenzhu/ListViewTest