优化 listview 有哪些方法?
21 个回答
为了避免大家误会这个回答只是老生常谈 ListView 的重用机制,编辑一下。
我这里说一下我用 ListView 的一些经验,为了尽量说的全面一些,这里列一些 Tips,具体的代码可以找相关的文章,或者一起交流:
- 首先,虽然大家都知道,还是提一下,利用好 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 在性能和可定制性上都有很大的改善,推荐使用。
- 有时候,需要从根本上考虑,是否真的要使用 ListView 来实现你的需求,或者是否有其他选择?
写的有点散,有些也是相互穿插的。这里面提到的都是一些原则,详细的解决方案,每个都能在网上找到相关的文章。
有一个小细节,很多开发人员都没有注意过
比如你的Item中有三个按钮,你要为三个按钮分别定义点击事件,如何定义?
也许你会在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...
}
});
}
通过传入的View v这个参数判断是哪一个button被点击,这样,无论View如何创建,你只创建了1个Listener对象
这只是一个小细节,优化的方式要综合使用,才会事半功倍
------------------7月24日更-----------------
v.getTag() 这个tag本不是用来存数据的,通俗点讲它和view 的Id是同一个东西,只不过tag的类型是Object。实际上在tag中存储数据是不符合规范的方式
但其实View类有两种tag,
setTag
(
Objecttag) 方法将tag保存在一个成员变量中,findViewWithTag正是遍历此tag
setTag(int key,
Objecttag) 方法是最终是调用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这个参数有关
不知道这回我表述清楚了没有