这篇文章只是简单记录一下昨天遇到的一个问题。
ListView
和RecyclerView
的复用,应该除了小白大家都知道了。但是昨天在使用下拉刷新加载数据的时候,我发现调用notifyDataSetChanged()
后loading动画会卡顿一下,约100-200毫秒。强迫症患者肯定不能忍,结合Choreographer
进行排查,发现是在getView(...)
的时候,inflate(...)
非常耗时。
大家熟知,如果是当前已经有了一页数据,convertView可以直接复用。但如果当前没有数据呢?这时候convertView为null,就一定会走inflate(...)
导致掉帧。但如果我们能提前完成inflate(...)
,后面来数据了直接复用就可以避免这个问题。
昨天经过郭霖大佬点播将ListView
设置为INVISIBLE
,默认加载一些空数据不显示出来,等正真的数据来了在设置为VISIBLE
。这算是一个比较好的思路了,提前inflate(...)
一页View,加载数据的时候直接复用即可。
但是我们现在来看一个使用场景:先隐藏加载了一页空数据,然后用户条件筛选,获取到数据集A,这时候数据集A为空,那么这些隐藏的item不会被复用,而是直接remove。然后用户更改条件再次请求数据,来了数据集B,这时候B不为空,getView(...)
的时候没有可复用的convertView,依然会inflate(...)
导致掉帧。这时候INVISIBLE
大法就不好用了。
经过仔细斟酌,我想如果在Adapter中自己缓存一个View集合,在前面这个使用场景,来数据集B的时候,如果convertView为null不可复用,我还可以从自己的缓存集合中获取。下面放出示例代码:
public class ExampleAdapter<T> extends BaseAdapter {
private final LayoutInflater mLayoutInflater;
private List<T> mDataList = new ArrayList<>();
private List<View> mConvertViewCaches = new ArrayList<>();
protected ExampleAdapter(Context context) {
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
if (mConvertViewCaches.size() == 0) {
return 999;
}
return mDataList.size();
}
@Override
public T getItem(int position) {
if (position >= 0 && position < mDataList.size()) {
return mDataList.get(position);
}
return null;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
for (View cache : mConvertViewCaches) {
if (cache.getParent() == null) {
convertView = cache;
break;
}
}
}
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.item_example, parent, false);
//省略viewHolder操作
convertView.setTag(viewHolder);
mConvertViewCaches.add(convertView);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
T item = getItem(position);
if (item != null) {
//省略viewHolder操作
}
return convertView;
}
}
代码很简单,就是多了一个mConvertViewCaches,用来缓存在getView(...)
中通过inflate(...)
解析出来的convertView,当convertView为null无法复用时,从mConvertViewCaches中获取可以复用的convertView,以减少inflate(...)
操作。同时,在getCount()
方法中通过判断mConvertViewCaches的大小,来区分是否ListView是否第一次加载,如果是第一次加载,返回一个足够大的数字来保证getView(...)
解析出足够一页的布局候用。另外注意避免数据越界以及做好非空判断即可。
最后总结一下,尽量避免频繁的inflate操作,尤其是有动画的情况下,掉帧非常明显。另外,RecyclerView
同ListView
,在Adapter中对convertView进行缓存即可,示例代码就不放出了。