
本文共 6949 字,大约阅读时间需要 23 分钟。
( ^ _ ^ )
ListView是个ViewGroup,可有多个子控件,由名字即可知道可以像列表一样展示子View。
设计上用到了适配器模式,可达到与适配器兼容和协同工作的作用。使得ListView使用起来只需定制适配器即可完成想要的展示。
我们先写以下怎么简简单单的使用,然后进行优化:
使用方法:
1、activity_main.xml
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout 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"> <ListView android:id="@+id/lv" android:layout_height="match_parent" android:layout_width="match_parent" /></androidx.constraintlayout.widget.ConstraintLayout>
2、创建adapter
class MyAdapter(val context: Context, val data:List<String>): BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var view:View view = View.inflate(context, R.layout.item_listview, null) val leftTv = view.findViewById<TextView>(R.id.leftTv) val rightTv = view.findViewById<TextView>(R.id.rightTv) rightTv.text = data.get(position) leftTv.text = position.toString() return view } override fun getItem(position: Int): Any { return data.get(position) } override fun getItemId(position: Int): Long { return position.toLong() } override fun getCount(): Int { return data.size }}
MainActivity:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val listView = findViewById<ListView>(R.id.lv) val list = getData() listView.adapter = MyAdapter(this, list) } private fun getData(): List<String> { val list = ArrayList<String>() for(i in 1..500){ list.add("itemData:" + i) } return list }}
效果:
效果很简单,我们还看了一下该应用程序的内存使用,发现随着滑动,内存的使用不断增大,而且怎么进行回收都回收不掉,哎,这问题就很大,跟你说,问题就出现在:
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var view:View view = View.inflate(context, R.layout.item_listview, null) val leftTv = view.findViewById<TextView>(R.id.leftTv) val rightTv = view.findViewById<TextView>(R.id.rightTv) rightTv.text = data.get(position) leftTv.text = position.toString() return view}
意思就是没创建一个条目,就实例化一个View对象,累积下来就很容易内存溢出,怎么解决呢?
我们发现在这个方法传进来了个convertView参数,当ListView初始化时,一个item就会创建一个convertView,直至达到item数(或者ListView所能容纳的item数量+1)。当我们上滑时新的item从下面移入,使用已有的convertView加载数据
;最上面的convertView移出屏幕,进行detach操作,下滑同理。
过程可以理解为下图:
所以我们就无需一直创建view,而是复用convertView实现内存优化,修改代码:
class MyAdapter(val context: Context, val data:List<String>): BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var view:View? = null if(convertView==null){ view = View.inflate(context, R.layout.item_listview, null) }else{ view = convertView } val leftTv = view?.findViewById<TextView>(R.id.leftTv) val rightTv = view?.findViewById<TextView>(R.id.rightTv) rightTv?.text = data.get(position) leftTv?.text = position.toString() return view!! } override fun getItem(position: Int): Any { return data.get(position) } override fun getItemId(position: Int): Long { return position.toLong() } override fun getCount(): Int { return data.size }}
很明显,只增加了点就不在增加了。
下一个问题,我们还发现,在每次对item中的数据设置时我们都会重新findViewById,这就会造成性能浪费,前面说了,我们的convertView只是进行detach,但不会清空内容,又因为每个item布局都是一样的,所以我们只需对其进行数据更新就可以了,没必要老是findViewById找子控件。方法:
class MyAdapter(val context: Context, val data:List<String>): BaseAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var view:View var viewHolder:ViewHolder? = null if(convertView==null){ view = View.inflate(context, R.layout.item_listview, null) viewHolder = ViewHolder() viewHolder?.leftTv = view.findViewById(R.id.leftTv) viewHolder?.rightTv = view.findViewById(R.id.rightTv) //与convertView绑定 view.setTag(viewHolder); }else{ view = convertView //获取viewHolder viewHolder = view.getTag() as ViewHolder? } viewHolder?.rightTv?.text = data.get(position) viewHolder?.leftTv?.text = position.toString() return view } override fun getItem(position: Int): Any { return data.get(position) } override fun getItemId(position: Int): Long { return position.toLong() } override fun getCount(): Int { return data.size } // 定义一个内部类,用于对控件的实例进行缓存 inner class ViewHolder { var leftTv: TextView? = null var rightTv: TextView? = null }}
定义内部类ViewHolder,每个convertView绑定自己的ViewHolder即可。
Android ListView异步加载图片乱序问题
原因:
我们用listView加载图片时通常时在getView()方法里开启异步请求获取图片,当获取比较耗时,这时如果快速滑动ListView导致该convertView移出屏幕,并且进入复用,然后又显示到了屏幕上,这时候这个convertView就会同时拥有两个图片请求,当第一个请求完成后会设置到该convertView的ImageView上,如果第二个请求也随之完成,就会导致我们看到的先显示一张图片,然后又变成了另外一张图片的情况。
解决方法:
一、
- 在getView()方法中用url地址给ImageView设置tag标记;
- 在onPostExecute()方法中调用 mListView.findViewWithTag(imageUrl) 获取ImageView实例,然后判断如果不为null,就把drawable图片设置到该ImageView控件上;因此在每次调用getView时,即使是同一个convertView,它里边的ImageView中的tag都是最新的url,如果是旧的请求就会找不到ImageView进行设置了。
如下:
private var mListView: ListView? = nulloverride fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { if (mListView == null){ mListView = parent as ListView? } var view:View var viewHolder:ViewHolder? = null if(convertView==null){ view = View.inflate(context, R.layout.item_listview, null) viewHolder = ViewHolder() viewHolder?.iv = view.findViewById(R.id.iv) //与convertView绑定 view.setTag(viewHolder); }else{ view = convertView //获取viewHolder viewHolder = view.getTag() as ViewHolder? } //打标记 viewHolder?.iv?.setTag(data.get(position)) viewHolder?.iv?.setImageDrawable(BitmapTask().execute(url)) return view}
inner class BitmapTask: AsyncTask<String, Void, BitmapDrawable>() { var imageUrl: String? = null override fun doInBackground(vararg params: String?): BitmapDrawable { imageUrl = params[0] //下载图片 ... } override fun onPostExecute(result: BitmapDrawable?) { val imageView = mListView?.findViewWithTag<ImageView>(imageUrl) if (imageView!=null&&result!=null){ imageView.setImageDrawable(result) } }}
二、使用NetworkImageView
NetworkImageView自身就已经考虑这个问题,并且可根据自身宽高和图片宽高进行图片压缩。NetworkImageView会把已经移出屏幕的convertView中的请求取消掉,达到处理乱序目的,但万一请求有时也是无法中断的。
三、使用弱引用
与设置Tag如出一辙,只不过这里是以ImageView和请求任务(弱引用)进行双向关联,当新的请求任务被设置到ImageView是,即可使之前的任务呗回收掉,目标达成。
参考:
guolin大佬的
发表评论
最新留言
关于作者
