ListView创建及优化

组成

构建一般的ListView包含三部分:

  • ListView布局(控件)
  • MainActivity(承载Activity或Fragment等)
  • Adapter适配器

Adapter

Adapter是适配器,一个通过泛型来指定要适配的数据类型,并在构造函数中将要适配的数据传入。可以简单理解为数据绑定,并将其显示在ListView上,可以理解类似于MVP中的Presenter。其作为View和“Model数据”之间的桥梁,ListView会在Adapter中得到需要的数据,并加载出来。而ListView只是一种列表的表现形式,将获取的数据以列表的方式进行展现。
通常我们会选用继承ArrayAdapter进行数组数据的展示,或者继承BaseAdapter进行自定义Adapter

ArrayAdapter

以简单、便捷的继承ArrayAdapter展开,快速构建一个ListView实例。

  1. 根据上述组成的三步,首先是ListView布局的添加。我们先构建一个id为listview_testListView
<?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>
  1. 紧接着是继承ArrayAdapterMyAdapter适配器构建,首先创建一个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
    }
}
  1. 最后是MainActivity,Java中通过setAdapterListView中传入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代码中未包含监听部分)

成果图
resultPic.png

性能优化

在上述方法中,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重新加载布局的问题,但是getViewView依旧会每次通过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)
}

convertViewnull时,调用View.setTag()将控件实例存到myViewHolder中,convertView不为null时通过getTag()把已有的控件myViewHolder取出来。(Kotlin语法,get\set可以直接view.tag代替)

Demo代码

GitHub:alicechenzhu/ListViewTest

参考引用

ListView和RecyclerView的使用和区别

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。