美文网首页
进一步优化ListView和RecyclerView的复用

进一步优化ListView和RecyclerView的复用

作者: SimonLeeeeeeeee | 来源:发表于2018-10-31 12:13 被阅读24次

    这篇文章只是简单记录一下昨天遇到的一个问题。

    ListViewRecyclerView的复用,应该除了小白大家都知道了。但是昨天在使用下拉刷新加载数据的时候,我发现调用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操作,尤其是有动画的情况下,掉帧非常明显。另外,RecyclerViewListView,在Adapter中对convertView进行缓存即可,示例代码就不放出了。

    相关文章

      网友评论

          本文标题:进一步优化ListView和RecyclerView的复用

          本文链接:https://www.haomeiwen.com/subject/byootqtx.html