RecyclerView使用指南(五)—— 實現吸頂效果

聲明:原創作品,轉載請註明出處: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使用指南(五)—— 實現吸頂效果》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章