ListView使用以及乱序问题如何解决
发布日期:2021-05-10 06:29:17 浏览次数:25 分类:原创文章

本文共 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上,如果第二个请求也随之完成,就会导致我们看到的先显示一张图片,然后又变成了另外一张图片的情况。

解决方法:

一、

  1. 在getView()方法中用url地址给ImageView设置tag标记;
  2. 在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大佬的

上一篇:Volley用法
下一篇:Android的Inflater使用

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2025年04月18日 15时34分17秒