美文网首页Android技术知识Android开发经验谈Android开发
RecyclerView使用指南(五)—— 实现吸顶效果

RecyclerView使用指南(五)—— 实现吸顶效果

作者: 28292f4144cb | 来源:发表于2018-11-27 08:43 被阅读175次

    声明:原创作品,转载请注明出处:https://www.jianshu.com/p/c197e3bf8329

    前一篇文字我讲解了ItemDecoration的使用方式,这篇文章默认大家已经读过RecyclerView使用指南(四)—— 使用ItemDecoration,所以,不熟悉ItemDecoration的同学请先去看前一篇文章。
    OK,我们先来看一下我们将要实现的效果:

    一、实现带有Section的样式

    我们先重写getItemOffsets()方法,增加outRect的高度,然后重写onDraw()方法,画出一个rectangle。代码如下:

    public class DemoItemDecoration extends RecyclerView.ItemDecoration {
        private int mSectionHeight = 80;
        private Paint mPaint;
    
        public DemoItemDecoration() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(Color.BLUE);
        }
    
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            outRect.top = mSectionHeight;
        }
    
        @Override
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDraw(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                float sectionLeft = parent.getLeft();
                float sectionTop = child.getTop() - mSectionHeight;
                float sectionRight = parent.getWidth();
                float sectionBottom = child.getTop();
                c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaint);
            }
        }
    
        @Override
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
        }
    }
    
    实现了这样一个效果:

    二、实现分组

    刚刚实现的每一个Item都有section,这与实际需求时不符的,那么我们要把数据进行分组,每一组的第一条Item上面才有section,这里,为了让ItemDecoration不与数据源发生直接关系,我们新增一个GroupBean类来描述是否需要增加section。如下:

    public class GroupBean {
        private int mGroupId;
        private int mGroupPosition;
        private boolean mIsFirst;
        private boolean mIsLast;
    
        public GroupBean(int groupId, int groupPosition, boolean isFirst, boolean isLast) {
            mGroupId = groupId;
            mGroupPosition = groupPosition;
            this.mIsFirst = isFirst;
            this.mIsLast = isLast;
        }
    
        public int getGroupId() {
            return mGroupId;
        }
    
        public int getGroupPosition() {
            return mGroupPosition;
        }
    
        public boolean isFirst() {
            return mIsFirst;
        }
    
        public boolean isLast() {
            return mIsLast;
        }
    }
    

    然后改写我们的getItemOffsets()方法和onDraw()方法,只有每一个分组的第一条Item才显示section。代码如下:

    public class DemoItemDecoration extends RecyclerView.ItemDecoration {
        private int mSectionHeight = 80;
        private Paint mPaintSection;
        private Paint mPaintText;
        private List<GroupBean> mGroupBeans;
    
        public DemoItemDecoration(List<GroupBean> groupBeans) {
            //数据
            mGroupBeans = groupBeans;
            //画笔
            mPaintSection = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaintSection.setColor(Color.BLUE);
            mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaintText.setTextSize(60);
            mPaintText.setColor(Color.YELLOW);
        }
    
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int position = parent.getChildAdapterPosition(view);
            if (mGroupBeans.get(position).isFirst()) {
                outRect.top = mSectionHeight;
            }
        }
    
        @Override
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDraw(c, parent, state);
        }
    
        @Override
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(child);
                GroupBean groupBean = mGroupBeans.get(position);
                if (mGroupBeans.get(position).isFirst()) {
                    float sectionLeft = parent.getLeft();
                    float sectionTop = child.getTop() - mSectionHeight;
                    float sectionRight = parent.getWidth();
                    float sectionBottom = child.getTop();
                    c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                    c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                }
            }
        }
    }
    

    最后,在Activity中进行ItemDecoration与GroupBean列表的绑定,以及ItemDecoration与RecyclerView的绑定:

        private void initRv() {
            List<GroupBean> groupBeans = new ArrayList<>();
            //根据RecyclerView的数据源,设置需要增加section的item
            for (Data data : mList) {
                //这里就是模拟一下,所以我取4的倍数增加section
                int i = mList.indexOf(data);
                int groupId = i / 4;
                int groupPosition = i % 4;
                GroupBean groupBean = null;
                //这里是假数据嘛,4的倍数有section,那余数是3的时候肯定是分组的最后一个啦
                if (groupPosition == 0) {
                    groupBean = new GroupBean(groupId, groupPosition, true, false);
                }
                if (groupPosition == 3) {
                    groupBean = new GroupBean(groupId, groupPosition, false, true);
                }
                groupBeans.add(groupBean);
            }
            RecyclerView recyclerView = findViewById(R.id.rv);
            recyclerView.setAdapter(new SingleItemAdapter(mList));
            recyclerView.addItemDecoration(new DemoItemDecoration(groupBeans));
        }
    
    我们来看下效果:

    好,这样,我们就实现了分组的效果,但是我们想要的吸顶效果,section是应该显示到Item图层的上方的,那么我们使用onDraw()方法来实现,显然是不合理的,既然如此,我们就将onDraw()方法中的内容剪切到onDrawOver()中好了~

    三、实现section在列表顶部悬浮

    实现吸顶效果,我们还需要做到让我们的section在列表顶部悬浮,来分析一下逻辑:

    • 每个分组的第一条数据需要有section
    • 列表的最上方必须显示一个section
      现在来修改一下onDrawOver(),实现一下:
    @Override
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(child);
                GroupBean groupBean = mGroupBeans.get(position);
                //所有分组的第一条数据有section
                if (groupBean.isFirst()) {
                    float sectionLeft = parent.getLeft();
                    float sectionTop = child.getTop() - mSectionHeight;
                    float sectionRight = parent.getWidth();
                    float sectionBottom = child.getTop();
                    c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                    c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                }
                //列表的最上方显示section信息(这里section是第一条显示的条目所对应的groupId)
                LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
                int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
                if (position == firstVisibleItemPosition) {
                    float sectionLeft = parent.getLeft();
                    float sectionTop = parent.getTop();
                    float sectionRight = parent.getWidth();
                    float sectionBottom = parent.getTop() + mSectionHeight;
                    c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                    c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                }
            }
        }
    
    来看下效果:

    嗯,我们实现了一个吸顶效果,但是两个section进行更替的特效显得比较粗糙啊,我们想要的是下面的section将上面的section顶上去,OK,我们再进行优化一下。

    四、优化section更替的特效

    我们仔细观察上面的效果图,当下面的section向上移动的时候,上面的section没有移动,所以,看起来下面的section直接覆盖到了它的上面。
    那么上面的section应该在什么时机进行移动呢?它的底边应该是在该分组中最后一个Item的底部的上方,所以,我们更改,当section的底部低于“分组中最后一个Item”时,section整体上移,移动的距离就是section的高度与条目底部的差。
    我们只需更改onDrawOver()方法的代码:

        @Override
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(child);
                GroupBean groupBean = mGroupBeans.get(position);
                //所有分组的第一条数据有section
                if (groupBean.isFirst()) {
                    float sectionLeft = parent.getLeft();
                    float sectionTop = child.getTop() - mSectionHeight;
                    float sectionRight = parent.getWidth();
                    float sectionBottom = child.getTop();
                    c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                    c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                }
                //列表的最上方显示section信息(这里section是第一条显示的条目所对应的groupId)
                LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
                int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
                if (position == firstVisibleItemPosition) {
                    //如果是本组的最后一条,section的底部就不能低于这个条目的底部
                    if (groupBean.isLast()) {
                        //当条目的底部已经高于section的时候,section应该随着条目的底部往上移动
                        if (child.getBottom() < mSectionHeight) {
                            float sectionLeft = parent.getLeft();
                            float sectionTop = parent.getTop() - (mSectionHeight - child.getBottom());
                            float sectionRight = parent.getWidth();
                            float sectionBottom = parent.getTop() + mSectionHeight - (mSectionHeight - child.getBottom());
                            c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                            c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                        } else {
                            float sectionLeft = parent.getLeft();
                            float sectionTop = parent.getTop();
                            float sectionRight = parent.getWidth();
                            float sectionBottom = parent.getTop() + mSectionHeight;
                            c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                            c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                        }
                    } else {
                        float sectionLeft = parent.getLeft();
                        float sectionTop = parent.getTop();
                        float sectionRight = parent.getWidth();
                        float sectionBottom = parent.getTop() + mSectionHeight;
                        c.drawRect(sectionLeft, sectionTop, sectionRight, sectionBottom, mPaintSection);
                        c.drawText(String.valueOf(groupBean.getGroupId()), sectionLeft, sectionBottom - 5, mPaintText);
                    }
                }
            }
        }
    

    在Activity中添加数据:

        private void initRv() {
            List<GroupBean> groupBeans = new ArrayList<>();
            //根据RecyclerView的数据源,设置需要增加section的item
            for (Data data : mList) {
                //这里就是模拟一下,所以我取4的倍数增加section
                int i = mList.indexOf(data);
                int groupId = i / 4;
                int groupPosition = i % 4;
                GroupBean groupBean;
                //这里是假数据嘛,4的倍数有section,那余数是3的时候肯定是分组的最后一个啦
                if (groupPosition == 0) {
                    groupBean = new GroupBean(groupId, groupPosition, true, false);
                } else if (groupPosition == 3) {
                    groupBean = new GroupBean(groupId, groupPosition, false, true);
                } else {
                    groupBean = new GroupBean(groupId, groupPosition, false, false);
                }
                groupBeans.add(groupBean);
            }
            RecyclerView recyclerView = findViewById(R.id.rv);
            recyclerView.setAdapter(new SingleItemAdapter(mList));
            recyclerView.addItemDecoration(new DemoItemDecoration(groupBeans));
        }
    
    最后看一下效果:

    总结

    这篇文章我们实现了一个吸顶效果的特效,是属于比较高级的用法了,关于ItemDecoration的用法也用它进行收尾了。另外,示例代码中的冗余代码比较多,主要是为了看起来容易理解,请小朋友们在使用过程中合理地优化代码。

    参考文献

    https://blog.csdn.net/briblue/article/details/70211942

    系列文章

    《RecyclerView使用指南(一)—— 基本使用》
    《RecyclerView使用指南(二)—— 多种ItemLayout》
    《RecyclerView使用指南(三)—— 添加分割线和点击事件》
    《RecyclerView使用指南(四)—— 使用ItemDecoration》
    《RecyclerView使用指南(五)—— 实现吸顶效果》

    相关文章

      网友评论

      • 教学平台杨教授:支持一下,消灭零评论:smile:
        教学平台杨教授:@Will_20151115 不谢,有空可以看看我写的文章哦,如果想学习更多有深度的知识,可以看我的作者简介,有惊喜哦:smile:
        28292f4144cb:哈哈哈,那你是最可爱的人喽~

      本文标题:RecyclerView使用指南(五)—— 实现吸顶效果

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