RecyclerView 速度优化, 多类型 item 以及上拉刷新

RecyclerView 是 Android 中最重要的控件之一, 本文主要内容为如何解决 RecyclerView 包含大量数据, 有多种 item 类型, item 中需要加载图片, 监听多个子 view 的事件等等问题是我如何处理的, 本文只是简单介绍了一下方法, 而具体没有过多描述, 实现的细节远远复杂的多.

一. 对 item 进行优化 减少 item layout 层次, 减少过度绘制, 避免多次 measure , layout

1.在定义 Item 布局的时候, 应该尽量减少布局的层次. 使用 support 包中的 ConstraintLayout 即可避免多层嵌套, 这也是 google 官方推荐的. 大部分情况下, ConstraintLayout 都可以满足我们的需求.

3.当 item 布局简单的时候, 尽量使用代码创建 item, 这可以很大程度的提高布局创建的速度.

4.当 item 复杂的时候, 使用自定义 view, 针对情况进行加载优化. 部分数据预加载(根据情况决定提前加载的 item 数量), 部分数据只在 item 可见时加载. 重写 onMeasure.

5.使用 addItemTouchListener 替换 在 onBinViewHolder 中给 item 绑定事件.

以下为在 RecyclerView 中监听 Item 的事件, 以及 item ViewHolder, position.

public class ItemTouchListener implements RecyclerView.OnItemTouchListener {
    private GestureDetectorCompat mGestureDetector;
    private RecyclerView mRecyclerView;
    public ItemTouchListener(final RecyclerView recyclerView){
        this.mRecyclerView = recyclerView;
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                detect(e, false);
                return true;
            }
            @Override
            public void onLongPress(MotionEvent e) {
                detect(e, true);
            }
            private void detect(MotionEvent e, boolean longClick){
                View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
                if (null != childViewUnder) {
                    Object tag = childViewUnder.getTag();
                    int position = mRecyclerView.getChildAdapterPosition(childViewUnder);

                    if (longClick){
                        onClick(mRecyclerView.getChildViewHolder(childViewUnder), position, tag);
                    }else{
                        onLongClick(mRecyclerView.getChildViewHolder(childViewUnder), position, tag);
                    }
                }
            }

        });
    }
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);
        return false;
    }
    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);
    }
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept){
    
    }
    public void onClick(RecyclerView.ViewHolder viewHolder, int position, Object tag){
    
    }
    public void onLongClick(RecyclerView.ViewHolder viewHolder, int position, Object tag){
    
    }
}

我们只需给 RecyclerView.addOnItemTouchListener() 根据需要重写 onClick 或者 onLongClick 方法即可

二. 针对 RecyclerView 优化

1.设置 item 等高, 如果 item 的高度是一样的, 则设置为等高可提高绘制速度

mRecyclerView.setHasFixedSize(true);

2.RecyclerViewPool 共用, 例如在一个 viewPager 中有多个 fragment, 或多个 recyclerview 并列, 其中的列表类型不同时, 可以使用同一个 recyclerviewpool 以复用, 减少创建不必要的 item, 但复用可能会出现很多问题, 使用时应注意.

3.避免在 onCreateViewHolder 和 onBindViewHolder 创建过多对象, 在滑动的时候回频繁地调用这两个方法, 如果创建过多临时对象则会引起频繁 gc , 造成卡顿

4.局部刷新, 在绑定数据的时候更新, 而不是瞬间加载完成所有数据

5.使用预加载, 为了避免在一瞬间创建大量数据造成卡顿, 我们可以先给 RecyclerView 创建一定数量的站位 Item , 这样在第一次加载的时候就只需要执行 onBindViewHolder 绑定数据, 在 onCreateViewHolder 中我们返回相同的 ViewHolder 即可.

6.对于性能较好的手机, 开启缓存可以很大程度的提高速度, 缓存过多会占用内存.

RecyclerView.setItemViewCacheSize(20);
RecyclerView.setDrawingCacheEnabled(true);
RecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

7.在 onBindViewHolder 中尽量只绑定数据, 避免对数据的计算比较, 新建对象. 例如, 对 item 添加事件监听不必每次都新建一个, 应创只创建一个, 在监听器内进行判断是哪个 item 发起的事件.

  1. 尽量避免在滑动时刷新数据

  2. 重写 Adapter 的 getItemId 方法, 为每个 item 绑定一个固定 id, 并且 adapter.setHasStableIds(true) 这样在 notifyDataSetChanged 的时候, 不会把所有的item 都重新修建, 相同 id 的 item 不会调用 onCreateViewHolder, 注意 id 代表着数据源唯一性.

三. 添加 页脚 页头.

这个非常简单, 只需要在 Adapter 中进行 itemType 判断返回不同值, 然后在onCreateViewHolder 中判断, 返回不同的view 即可. 下面是我一个项目中的实例. 只需要调用 adapter.setHeaderView 即可添加页头.

public class ReplyRecyclerViewAdapter extends RecyclerView.Adapter小于RecyclerView.ViewHolder大于 {

    private final int HEADER = 1;
    final int FOOTER = 2;
    private List小于Reply大于 mReplies;
    private Context context;
    private FrameLayout.LayoutParams mLayoutParams;

    private ViewGroup mHeaderViewGroup;

    public ReplyRecyclerViewAdapter(Context context, List小于Reply大于 replies){

        this.context = context;
        this.mReplies = replies;
    }

    public Context getContext() {
        return context;
    }

    public void setHeaderView(ViewGroup view){
        mHeaderViewGroup = view;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        if (viewType == HEADER){
            return new HeaderViewHolder(mHeaderViewGroup);
        }
        ReplyView replyView = new ReplyView(parent.getContext());
        replyView.setLayoutParams(mLayoutParams);
        return new ItemViewHolder(replyView);
    }

    @Override
    public int getItemViewType(int position) {

        if (position == 0 && mHeaderViewGroup != null){
            return HEADER;
        }
        if (position == getItemCount()){
            return FOOTER;
        }
        return 0;
    }

    public void addReplies(List小于Reply大于 replies){

        if (replies.size() == mReplies.size() &&
                (mReplies.size() != 0 && replies.get(0).getId() == mReplies.get(0).getId())){
            return;
        }
        mReplies.addAll(replies);
        notifyDataSetChanged();
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

        if (holder instanceof ItemViewHolder){
            int p = ((mHeaderViewGroup!=null) ? position-1 : position);
            ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
            Reply reply = mReplies.get(p);
            reply.setFloor(position-1);
            itemViewHolder.replyView.setReply(reply);
        }
    }

    @Override
    public int getItemCount() {
        return mReplies.size() + (mHeaderViewGroup == null ? 0 : 1);
    }

    static class ItemViewHolder extends RecyclerView.ViewHolder{

        ReplyView replyView;
        ItemViewHolder(View itemView) {
            super(itemView);
            this.replyView = (ReplyView) itemView;
        }
    }

    static class HeaderViewHolder extends RecyclerView.ViewHolder{

        ViewGroup viewGroup;
        HeaderViewHolder(View itemView) {
            super(itemView);
            this.viewGroup = (ViewGroup) itemView;
        }
    }
}

添加上拉刷新

上拉刷新是基于上面的适配器进行装饰的实现. 当刷新的时候, 就会调用 OnPullUpListener.onPullUp(), 在使用该 adapter 的地方实现该接口即可, 并添加相关刷新的代码即可.

public class PullRefreshReplyAdapter extends RecyclerView.Adapter小于RecyclerView.ViewHolder大于 {

    private ReplyRecyclerViewAdapter mAdapter;

    private ViewGroup mFooterViewGroup;
    private FooterStatus mStatus = FooterStatus.LOADING;
    private OnPullUpListener mOnPullUpListener;

    public enum FooterStatus{
        LOADING, HIDDEN, COMPLETE
    }

    public PullRefreshReplyAdapter(Context context, List小于Reply大于 replies){
        this(new ReplyRecyclerViewAdapter(context, replies));
    }

    public PullRefreshReplyAdapter(ReplyRecyclerViewAdapter adapter){
        this.mAdapter = adapter;

        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
        mFooterViewGroup = new FrameLayout(mAdapter.getContext());
        mFooterViewGroup.setLayoutParams(layoutParams);
        ProgressBar progressBar = new ProgressBar(mAdapter.getContext());
        progressBar.setLayoutParams(new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 80));
        mFooterViewGroup.addView(progressBar);
        mFooterViewGroup.setVisibility(View.INVISIBLE);
    }

    public void setStatus(FooterStatus mStatus) {
        this.mStatus = mStatus;
        if (mStatus == FooterStatus.COMPLETE){
            mFooterViewGroup.removeAllViews();
            TextView textView = new TextView(mAdapter.getContext());
            textView.setText(mAdapter.getContext().getText(R.string.no_more));
            textView.setTextSize(15);
            textView.setLayoutParams(new FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
            textView.setGravity(Gravity.CENTER);
            mFooterViewGroup.addView(textView);
        }
    }
    public void setOnPullUpListener(OnPullUpListener onPullUpListener) {
        this.mOnPullUpListener = onPullUpListener;
    }
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == mAdapter.FOOTER){
            return new FooterViewHolder(mFooterViewGroup);
        }else{
            return mAdapter.onCreateViewHolder(parent, viewType);
        }
    }
    public void setHeaderView(LinearLayout mLlHeader) {
        mAdapter.setHeaderView(mLlHeader);
    }
    public void notifyRangeChanged(int start, int count){
        mAdapter.notifyItemRangeChanged(start, count);
    }
    public void notifyItem(int position){
        mAdapter.notifyItemChanged(position);
    }
    public void notifyAllDataChanged(){
        mAdapter.notifyDataSetChanged();
    }
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof FooterViewHolder && position 大于 10){
            switch (mStatus){
                case HIDDEN:
                    mFooterViewGroup.setVisibility(View.GONE);
                    break;
                case COMPLETE:
                    mFooterViewGroup.setVisibility(View.VISIBLE);
                    break;
                case LOADING:
                    mFooterViewGroup.setVisibility(View.VISIBLE);
                    mOnPullUpListener.onPullUp();
                    break;
            }
        }else{
            mAdapter.onBindViewHolder(holder, position);
        }
    }
    public interface OnPullUpListener{
        void onPullUp();
    }
    @Override
    public int getItemViewType(int position) {
        return mAdapter.getItemViewType(position);
    }
    @Override
    public int getItemCount() {
        return mAdapter.getItemCount() + 1;
    }
    private static class FooterViewHolder extends RecyclerView.ViewHolder{

        ViewGroup viewGroup;
        FooterViewHolder(View itemView) {
            super(itemView);
            this.viewGroup = (ViewGroup) itemView;
        }
    }
}

(完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容