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" android:orientation="vertical"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout>
接下来修改MainActivity代码
class MainActivity : AppCompatActivity() { private val data= listOf("数据1","数据2","数据1","数据1","数据1","数据1","数据1","数据1","数据1","数据1") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // supportActionBar?.hide() val adapter=ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data) listview.adapter=adapter } }
因为ListView一般是用来显示大量数据的,所以我们应该先将数据准备好。这些数据一般是从网上获取,或者读取本地数据库,但是这里为了方便就直接创建一个集合就好了。
不过,集合中的数据无法直接传递给ListView。我们需要借助适配器来完成。
使用simple_list_item_1只能单独显示一段数据,显然不符合我们在开发过程中的需求。因此我们需要进行对ListView的界面进行定制。
接下来,我们定义一个水果(Fruit)实体类。
class Fruits(val name:String,val imageId:String) { }
水果类中有两个字段,名字和图片。
然后我们为ListView 的子项指定一个自定义的布局。在Layout先新建一个fruit_item.xml的文件。
<?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:layout_width="35dp" android:layout_height="35dp" android:id="@+id/fruitImage" android:layout_gravity="center" android:layout_marginLeft="10dp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/fruitName" android:layout_gravity="center_vertical" android:layout_margin="10dp"/> </LinearLayout>
接下来创建一个适配器FruitAdapter
代码如下
class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(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)//获取当前项得Fruit实例 if (fruit!=null){ fruitImage.setImageResource(fruit.imageId) fruitName.text=fruit.name } return view } }
在使用getView中,使用Layout Inflater来为这个子项价值我们传入得布局。Layout Inflater 的inflater()接收三个参数,第三个参数设置为false 表示只让我们在父布局中声明的layout属性生效。但不会为这个View添加父布局。因为View一旦有了父布局它就就不能添加
到ListView中。
最后修改MainActivity中的代码。
class MainActivity : AppCompatActivity() { private val fruits=ArrayList<Fruits>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // supportActionBar?.hide() initFruits() //初始化列表数据 val adapter=FruitAdapter(this,R.layout.fruit_item,fruits) listview.adapter=adapter } private fun initFruits(){ repeat(2){ for (i in 0..20){ fruits.add(Fruits("测试一",R.mipmap.ic_launcher)) } } } }
可以看到这里添加了一个initFruits方法。用于初始化数据。
其中 repeat是Kotlin相对于Java新加入的特性,取代for(int i=0;i<5;i++)
用于简单的重复工作。所以这里有两个循环。呃。。不过不要在意这些细节。
如何提升ListView 的运行效率。这个问题,去面试的时候经常会问到。
目前我们的List View运行效率是很低的。因为在FruitAdapter的getView()方法中,每次都将布局重新加载一遍。当ListView快速滚动的时候,就会成为性能的瓶颈。
getView中还有个convertView的参数。这个参数用于将之前的加载好的布局进行缓存。以便之后进行重用。我们可以借助这个参数进行性能优化。接下来修改这个适配器的代码:
class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(activity,resourceId,data){ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var view:View if (convertView==null){ view=LayoutInflater.from(context).inflate(resourceId,parent,false) }else{ view=convertView } 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 } }
我们对getView中的方法进行了判断,如果converView为空,则私用LayoutInflater去加载布局,如果不为空则对convertVIew进行重用。这样可以提高ListView的效率。但是现在每次在getView中还是会调用View 中的findViewById方法来获取控件实例。
我们可以借助一个ViewHolder来对这部分性能进行优化。继续修改FruitAdapter中代码。如下:
class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(activity,resourceId,data){ inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView) override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var 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)//获取当前项得Fruit实例 if (fruit!=null){ viewHolder.fruitImage.setImageResource(fruit.imageId) viewHolder.fruitName.text=fruit.name } return view } }
可以看到我们新增一个ViewHolder的内部类。用于对子项布局的控件进行实例缓存。
经过这番操作,ListView的运行效率提升了不少。
除此之外还可以
4.将ListView的scrollingCache和animateCache设置为false
scrollingCache: scrollingCache本质上是drawing cache,你能够让一个View将他自己的drawing保存在cache中(保存为一个bitmap),这样下次再显示View的时候就不用重画了,而是从cache中取出。默认情况下drawing cahce是禁用的。由于它太耗内存了,可是它确实比重画来的更加平滑。
而在ListView中,scrollingCache是默认开启的,我们能够手动将它关闭。
animateCache: ListView默认开启了animateCache,这会消耗大量的内存,因此会频繁调用GC,我们能够手动将它关闭掉
优化前的ListView
<ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:cacheColorHint="#00000000" android:divider="@color/list_background_color" android:dividerHeight="0dp" android:listSelector="#00000000" android:smoothScrollbar="true" android:visibility="gone" />
优化后:
<ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="@color/list_background_color" android:dividerHeight="0dp" android:listSelector="#00000000" android:scrollingCache="false" android:animationCache="false" android:smoothScrollbar="true" android:visibility="gone" />
ListVIew的点击事件。
使用setOnItemClickListener()方法注册一个监听器,用户点击时,回调到Lambda表达式中,通过position参数判断用户点击哪个item
在MainActivity中修改,代码如下:
class MainActivity : AppCompatActivity() { private val fruits=ArrayList<Fruits>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // supportActionBar?.hide() initFruits() //初始化列表数据 val adapter=FruitAdapter(this,R.layout.fruit_item,fruits) listview.adapter=adapter listview.setOnItemClickListener { parent,view,position,id -> val fruit=fruits[position] Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show() } } private fun initFruits(){ repeat(2){ for (i in 0..20){ fruits.add(Fruits("测试一",R.mipmap.ic_launcher)) } } } }
重新运行,就可以看到,每点击一个item,就会弹出一个Toast提升你点击的那个item的name。
观察上面代码,我们发现声明4个参数,但我们只用了一个,这个时候我们可以将item的监听事件改写为
listview.setOnItemClickListener { _,_ ,position,_ -> val fruit=fruits[position] Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show() }