RecyclerView.Adapter优化了吗?

作者: 陈宇明 | 来源:发表于2016-04-10 15:55 被阅读24089次

    昨天写了一篇「还在用ListView?」讲的内容是RecyclerView的使用技巧以及一些常用的开源库,有朋友反馈“我已经在用recyclerview了”,那么如何让它更好用呢?此时我想到了优化RecyclerView.Adapter,因为在RecyclerView还没出来之前我就写过一篇「ListView之Adapter优化」,通过这篇文章的优化思路可以在原来的代码上修改部分代码用在优化RecyclerView.Adapter上,一如既往的好用。

    本次主要讲两个方面的优化

    • 精简代码
    • 扩展功能

    精简代码

    正常没优化的写法:

    public class DefAdpater extends RecyclerView.Adapter<DefAdpater.ViewHolder> {
        private final List<Status> sampleData = DataServer.getSampleData();
        private Context mContext;
        public DefAdpater(Context context) {
            mContext = context;
        } 
       @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.tweet, parent, false);
            return new ViewHolder(item);
        }
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Status status = sampleData.get(position);
            holder.name.setText(status.getUserName());
            holder.text.setText(status.getText());
            holder.date.setText(status.getCreatedAt());
            Picasso.with(mContext).load(status.getUserAvatar()).into(holder.avatar);
            holder.rt.setVisibility(status.isRetweet() ? View.VISIBLE : View.GONE);
        }
        @Override
        public int getItemCount() {
            return sampleData.size();
        }
        public static class ViewHolder extends RecyclerView.ViewHolder {
            private ImageView avatar;
            private ImageView rt;
            private TextView name;
            private TextView date;
            private TextView text;
            public ViewHolder(View itemView) {
                super(itemView);
                text = (TextView) itemView.findViewById(R.id.tweetText);
                name = (TextView) itemView.findViewById(R.id.tweetName);
                date = (TextView) itemView.findViewById(R.id.tweetDate);
                avatar = (ImageView) itemView.findViewById(R.id.tweetAvatar);
                rt = (ImageView) itemView.findViewById(R.id.tweetRT);
            }
        }
    }
    

    优化后,是这样的:

    public class QuickAdapter extends BaseQuickAdapter<Status> {
        public QuickAdapter(Context context) {
            super(context, R.layout.tweet, DataServer.getSampleData());
        }
        @Override
        protected void convert(BaseAdapterHelper helper, Status item) {
            helper.setText(R.id.tweetName, item.getUserName())
                    .setText(R.id.tweetText, item.getText())
                    .setText(R.id.tweetDate, item.getCreatedAt())
                    .setImageUrl(R.id.tweetAvatar, item.getUserAvatar())
                    .setVisible(R.id.tweetRT, item.isRetweet())
                    .linkify(R.id.tweetText);
        }
    }
    

    优化前和优化后的代码量是3:1的比例!

    我的天啦!太不可思议了!

    现在来分析,如何优化的?(带着问题学习)
    思路:
    找到重复部分代码,抽取到基类,非重复部分用抽象方法代替,具体让子类实现。
    说了思路在看看具体代码BaseQuickAdapter里面怎么写的:

    @Override
    public int getItemCount() {
        return data.size();
    }
    @Override
    public BaseAdapterHelper onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false);
        return new BaseViewHolder(context, item);
    }
    @Override
    public void onBindViewHolder(BaseViewHolder holder, final int position) {
        convert(holder, data.get(position));
    }
    protected abstract void convert(BaseViewHolder helper, T item);
    

    接下来再看看BaseViewHolder怎么写的:

    public class BaseViewHolder extends RecyclerView.ViewHolder {
        private final SparseArray<View> views;
        private final Context context;
        private View convertView;
    
        protected BaseViewHolder(Context context, View view) {
            super(view);
            this.context = context;
            this.views = new SparseArray<View>();
            convertView = view;
        }
        protected <T extends View> T retrieveView(int viewId) {
            View view = views.get(viewId);
            if (view == null) {
                view = convertView.findViewById(viewId);
                views.put(viewId, view);
            }
            return (T) view;
        }
        public BaseViewHolder setText(int viewId, CharSequence value) {
            TextView view = retrieveView(viewId);
            view.setText(value);
            return this;
        }
        public BaseViewHolder setImageUrl(int viewId, String imageUrl) {
            ImageView view = retrieveView(viewId);
            Picasso.with(context).load(imageUrl).into(view);
            return this;
        }
        public BaseViewHolder setVisible(int viewId, boolean visible) {
            View view = retrieveView(viewId);
            view.setVisibility(visible ? View.VISIBLE : View.GONE);
            return this;
        }
        public BaseViewHolder linkify(int viewId) {
            TextView view = retrieveView(viewId);
            Linkify.addLinks(view, Linkify.ALL);
            return this;
        }
        //此处省略若干常用赋值常用方法
    }
    

    利用SparseArray来做缓存,把常用方法全部写好,从而避免冗余代码。


    扩展功能

    大家都知道RecyclerView没有ItemClick方法,可以在上面提过的BaseQuickAdapter里面添加ItemClick,可以这样:

    网上有很多写法都是在onBindViewHolder里面写,功能是可以实现但是会导致频繁创建,应该在 onCreateViewHolder()中每次为新建的 View 设置一次就行了。

    private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;
    
    public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) {
        this.onRecyclerViewItemClickListener = onRecyclerViewItemClickListener;
    }
    
    public interface OnRecyclerViewItemClickListener {
        public void onItemClick(View view, int position);
    }
    
    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // init ViewHolder ...
        if (onRecyclerViewItemClickListener != null) {
            holder.getView().setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onRecyclerViewItemClickListener.onItemClick(v, holder.getLayoutPosition());
                }
            });
        }
    }
    

    还可以添加一些常用的方法如:

    public void remove(int position) {
        data.remove(position);
        notifyItemRemoved(position);
    }
    public void add(int position, T item) {
        data.add(position, item);
        notifyItemInserted(position);
    }
    

    代码我已经上传到GitHub上了,有兴趣的同学Star或者一起共同将它完成的更完善!送大家一句我非常喜欢的话:不分享谁与你共享呢?

    源码地址:传送门

    相关文章

      网友评论

      • 相互交流:这么看起来和红神的一样一样的啊
      • 247190e99b46:CoordinatorLayout+AppBarLayout+recyclerview时每次大幅度滑动后第一下的点击事件是失效的,第二下点击就正常了这是什么原因?
        a33fa1e5e1c5:这个问题应该不是库的问题 参考一下这个解决方案https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2
      • z_ym:这是简化,并不是优化
      • 性冷淡_:但是我需要数据的回调,在onBindViewHolder里面处理合适吗?
      • lucky_幸运:在做分组列表的时候,怎样固定第一层在顶部呢。l
      • xiayuexingkong:向大神学习ing~
      • Ming_明就:感谢博主的分享。
        刚看了下BaseViewHolder的源码,对于 #setText 或者 #setImageRecource传递的viewId,对于封装者的思路是清晰的,希望通过id获取到TextView或者ImageView,是否考虑在
        view = itemView.findViewById(viewId); 之后对view判断是否是T类型加一层保护~
      • 064b9db7d22c:大神,请问下有源码demo吗???
      • a8d0c67f6bc3:很厉害的东西。不过我不知道哪里设置的问题,在上拉加载倒一定数目的时候,我是第11页,每页10条,总是会在第11页的时候出问题,要么请求不了,要么突然多出来很多条目。
        陈宇明:具体看下你对数据源的处理。
      • da27c260cc85:Item复用的条件是怎样的呢?
      • FynnJason:大神你好,非常谢谢你的文章。我有个问题想咨询你,如果使用了ButterKnife,那要怎么处理helper中传入的id呢?
        陈宇明:@夜深未眠情已深 自定义ViewHolder
        陈宇明:@夜深未眠情已深 最新版本已经支持自定义ViewHolder了。
      • 68768b474bfc:作者,能否交流一下,这里的ViewHolder虽然用SparseArray来做缓存可以让封装的BaseAdpater更省代码,但是这样封装对性能的影响大不大,或者说是否值得呢?我平时的解决办法是用AndroidStudio的自定义模板和插件自动生成Adapter这种模板代码。
        https://github.com/TellH/AndroidStudioTemplate
        陈宇明:@TellH 经常测试,并不大。
      • f126000733b0:在抽取Adapter时候,ViewHolder不是静态的就会报onBindViewHolder没有被复写,
        或者把Adapter泛型去了,ViewHolder不用静态的页能通过编译,这是什么原因?
      • wo883721:我个人觉得有问题的地方啊:
        1.protected <T extends View> T retrieveView(int viewId)方法中,view = convertView.findViewById(viewId)后,没有判断view是否为空啊,如果viewid找不到的话,后面的view强转不会出问题么?
        2. remove(int position)和add(int position, T item)中,对只是调用了notifyItemRemoved和notifyItemInserted。但是这两个方法,其实只会触发RecyclerView的ItemAnimator的动画事件的。它不会刷新RecyclerView的,所以就会导致data和RecyclerView中有的Item数据会不一样,会出现很多奇怪的问题的。最好在之后调用notifyItemRangeChanged(int positionStart, int itemCount)方法,来通知RecyclerView刷新界面。
        陈宇明:@wo883721 感谢提出建议,我会验证后,并且做出修复。
      • AndroidWanLi:如果是多选删除呢,这个能封装进去吗
        陈宇明: @万力 可以
      • petma:不错,受教了
      • JackTangIT:多个item的话 就不好用了
        陈宇明:@JackTangIT 有关于多个item的使用方式,http://www.jianshu.com/p/cf29d4e45536
      • ee6c7c025709:onBindViewHolder() 中频繁创建新的 onClickListener 实例没有必要,应该在 onCreateViewHolder() 中每次为新建的 View 设置一次就行了。此外,onClickListener 在具体回调 onClick() 中没有执行 onRecyclerViewItemClickListener 非空判断,可能有隐患。整体感觉还不错,期待下篇。
        longzekai:不错,又学到了新技能。
        陈宇明:结果实践,确实可以和你说的一样在 onCreateViewHolder() 中每次为新建的 View 设置onClickListener, :+1: ,非常感谢宝贵建议!我这就去改改。
        陈宇明:@torchmu 多谢提建议,回头我可以试试你的方案,结果到时候和你讨论。在bind里面设置是出于要给位置考虑的。
      • 7d58a8dbe725:必须打赏,给你创作的动力
        陈宇明:@PSY红茶 非常感谢!
      • Max____:赞一个,比我的简洁很多。
      • 但悟我心:看了你的这篇和上篇,不错哦,打赏支持
        陈宇明:@但悟我心 非常感谢!
      • 星际之痕:这种写法的优化,主要就是代码简洁方面的优化吗??对性能有优化吗?
        星际之痕:@陈宇明 嗯
        陈宇明:@星际之痕 只是优化代码
      • wille_89:写的非常棒
      • 会飞的大象:倘若有多个布局类型,以及onbindview中有复杂的逻辑呢
        陈宇明: @skylineTan 很快的,我的开源项目已经集成了,后续会有相关文章。
        ce1dd2756b0f:@陈宇明 期待后续给出多个布局类型的优化~
        陈宇明:@会飞的大象 后续我会提到的😊
      • lhyz:在OnBindViewHolder中设置监听这种匿名内部类的话,onBindViewHolder频繁会调用导致性能问题吗?
        lhyz:@陈宇明 👍
        陈宇明:@lhyz 我测试过原生和这种封装的性能测试,几乎差不多。

      本文标题:RecyclerView.Adapter优化了吗?

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