- 首先,虽然大家都知道,还是提一下,利用好 convertView 来重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View。ListView 中有一个回收器,Item 滑出界面的时候 View 会回收到这里,需要显示新的 Item 的时候,就尽量重用回收器里面的 View。
- 利用好 View Type,例如你的 ListView 中有几个类型的 Item,需要给每个类型创建不同的 View,这样有利于 ListView 的回收,当然类型不能太多;
- 尽量让 ItemView 的 Layout 层次结构简单,这是所有 Layout 都必须遵循的;
- 善用自定义 View,自定义 View 可以有效的减小 Layout 的层级,而且对绘制过程可以很好的控制;
- 尽量能保证 Adapter 的 hasStableIds() 返回 true,这样在 notifyDataSetChanged() 的时候,如果 id 不变,ListView 将不会重新绘制这个 View,达到优化的目的;
- 每个 Item 不能太高,特别是不要超过屏幕的高度,可以参考 Facebook 的优化方法,把特别复杂的 Item 分解成若干小的 Item,特别推荐看一下这个文章:https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/
- 为了保证 ListView 滑动的流畅性,getView() 中要做尽量少的事情,不要有耗时的操作。特别是滑动的时候不要加载图片,停下来再加载,这个库可以帮助你 Glide:https://github.com/bumptech/glide
- 使用 RecycleView 代替。 ListView 每次更新数据都要 notifyDataSetChanged(),有些太暴力了。RecycleView 在性能和可定制性上都有很大的改善,推荐使用。
也许你会在getView中这样做
button1.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});
button2.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});
button3.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});
如果你每屏显示7个Item,你一共创建了21个listener对象在内存中,如果View回收不畅,会更多,这样,在滚动的时候频繁GC 就会导致卡顿(这里描述有误,请看7月25日更新)
如果你在Adapter初始化的时候创建一个Listener
public MyAdapter () {
myListener = new View.OnClickListener() {
@override
public void onClick(View v) {
v.getTag()
v.getId()
//balabalabala...
}
});
}
这只是一个小细节,优化的方式要综合使用,才会事半功倍
------------------7月24日更-----------------
v.getTag() 这个tag本不是用来存数据的,通俗点讲它和view 的Id是同一个东西,只不过tag的类型是Object。实际上在tag中存储数据是不符合规范的方式
但其实View类有两种tag,
setTag(Object tag) 方法将tag保存在一个成员变量中,findViewWithTag正是遍历此tag
setTag(int key, Object tag) 方法是最终是调用View类中的setKeyedTag(int key, Object tag)
这是一个私有方法
他是用 SparseArray 实现的,我们可以把需要的东西存到这里面(其实观察源码可以发现,系统很多时候都是这样做的)
这里要注意一点,参数key必须是唯一的,那么我们可以这样做
那么需要先在res/values/strings.xml中添加
<resources>
<item type="id" name="tag_first"></item>
<item type="id" name="tag_second"></item>
</resources>- 使用的时候写成
view.setTag(R.id.tag_first, obj1);
view.setTag(R.id.tag_second, obj2);
有人问,如果这样写,所有button只能通过id区分逻辑,无法传入每个item的数据
我们可以将数据通过view 的tag带进来
public View getView(.....) {
....
v.setTag(key, getItem(position));
....
}
然后在listener中通过v.getTag()将数据取出
-----------------------7月25日update-------------------
这里我有一个失误
如果listener里面的逻辑与当前的item有关,那么其实并不只是创建了21个listener对象
public void getView (View convertView ,final int position ....) {
if (convertView == null) {
View v = LayoutInflater.from(mContext).inflate(...);
v.setOnclickListener(new View.OnClickListener () {
@override
public void onClick(View v) {
getItem(position);
}
});
} else { }
}
你看下,如果绑定数据在convertView为空的情况下确实只创建了有限个listener,
但是在这种情况下绑定上的数据只有View创建时的7个,之后不为空的情况下没有更换listener导致重用的view数据是新的,listener里面的position依然是过去创建view时的7个之一,不会变化(注意参数上的final)
若在else里面再重新setListener,view是有重用,listener被换成新的,并与新的position绑定,老的listener就会变成一个废对象,等待gc回收,随着list滚动,越来越多
关键是我们的业务与position这个参数有关
1.重用 convertView
用以避免重复创建 View,重复创建 View 代价较大,而且如果重用 view 不改变宽高,重用View可以减少重新分配缓存造成的内存频繁分配/回收;
2. 避免在 getView 中有 重复调用的 findViewById
findViewById 的实现是遍历,如果你定义的 View 越复杂代价越大。
Google 推荐的做法是用 ViewHolder,然后保存在 view 的 tag 中。现在 RecyclerView 也是强制使用 ViewHolder 了。
3. 设置 View (如 TextView#setText )之前先对比数据是否有改变
一般来说,【比较两个数据的代价】远小于【 View 的重绘的代价】
4. 避免在 getView 函数中直接加载 Image 或做其他比较耗时的操作
加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作。
用户快速滑动列表时,会大量调用 getView ,而 getView 是在主线程中被调用的。如果你在 getView 函数中直接加载 Image 或做其他耗时操作,就会造成滑动比较卡。加载 ImageView 的解决方案就是开一个线程去把做这事。有很多第三库可以做这事。
5. ListView 中元素避免半透明
半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。在设计上能不半透明就不不半透明。实在要弄的话我个人是用个比较偷懒的方法,是在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
6. 尽量开启硬件加速
硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。
当然这一条不只是对 ListView。
7.用 ListView 威力加强版 -- RecyclerView
更多的新武将,更多的姿势,更规范的使用,更好用的动画,更加强大的变化