RecyclerView 知識(shí)梳理(4) - ItemDecoration

一、概述

通過ItemDecoration,可以給RecyclerView或者RecyclerView中的每個(gè)Item添加額外的裝飾效果,最常用的就是用來為Item之間添加分割線,今天,我們就來一起學(xué)習(xí)有關(guān)的知識(shí):

  • API
  • DividerItemDecoration解析
  • 自定義ItemDecoration

二、API介紹

當(dāng)我們實(shí)現(xiàn)自己的ItemDecoration時(shí),需要繼承于ItemDecoration,并根據(jù)需要實(shí)現(xiàn)以下三個(gè)方法:

2.1 public void onDraw(Canvas c, RecyclerView parent, State state)

  • canvasRecyclerViewcanvas
  • parentRecyclerView實(shí)例
  • StateRecyclerView當(dāng)前的狀態(tài),值包括START/LAYOUT/ANIMATION

所有在這個(gè)方法中的繪制操作,將會(huì)在itemViews被繪制之前執(zhí)行,因此,它會(huì)顯示在itemView之下。

2.2 public void onDrawOver(Canvas c, RecyclerView parent, State state)

2.1方法類似,區(qū)別在于它繪制在itemViews之上。

2.3 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

通過outRect,可以設(shè)置item之間的間隔,間隔區(qū)域的大小就是outRect所指定的范圍,view就是對(duì)應(yīng)位置的itemView,其它的參數(shù)解釋和上面相同。

三、DividerItemDecoration解析

3.1 使用方法

上面我們解釋了需要重寫的方法以及其中參數(shù)的含義,下面,我們通過官方自帶的DividerItemDecoration,來進(jìn)一步加深對(duì)這些方法的認(rèn)識(shí)。
DividerItemDecoration是為LinearLayoutManager提供的分割線,在創(chuàng)建它的時(shí)候,需要指定ORIENTATION,這個(gè)方向應(yīng)當(dāng)和LinearLayoutManager的方向相同。

    private void init() {
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        mTitles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mTitles.add(String.valueOf(i));
        }
        BaseAdapter baseAdapter = new BaseAdapter(mTitles);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(baseAdapter);
    }

最終展示的效果為:


3.2 源碼解析

3.2.1 繪制

DividerItemDecoration重寫了基類當(dāng)中的onDraw方法,也就是說這個(gè)分割線是在itemView之前繪制的:

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

我們先看縱向排列的RecyclerView分割線:

    @SuppressLint("NewApi")
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        //首先保存畫布
        canvas.save();
        final int left;
        final int right;
        //確定左右邊界的范圍,如果RecyclerView不允許子View繪制在Padding內(nèi),那么這個(gè)范圍為去掉Padding后的范圍
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            //獲得itemView的范圍,這個(gè)范圍包括了margin和offset,它們被保存在mBounds當(dāng)中
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            //需要考慮translationY和translationY
            final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
            //由于是垂直排列的,因此上邊界等于下邊界減去分割線的高度.
            final int top = bottom - mDivider.getIntrinsicHeight();
            //設(shè)置divider和范圍
            mDivider.setBounds(left, top, right, bottom);
            //繪制.
            mDivider.draw(canvas);
        }
        //回復(fù)畫布.
        canvas.restore();
    }

整個(gè)過程分為三步:

  • 確定子ViewRecyclerView中的繪制范圍
  • 確定每個(gè)子View的范圍
  • 確定mDivider的繪制范圍

下圖就是最終計(jì)算的結(jié)果:


橫向排列的RecyclerView列表和上面的原理是相同的,區(qū)別就在于計(jì)算mDivider.setBounds的計(jì)算:

//....
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child));
final int left = right - mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
//..

3.2.2 邊界處理

從上面的分析可以知道,如果將divider直接繪制在itemView的范圍內(nèi),那么由于我們是先繪制divider,再繪制itemView的內(nèi)容的,那么它就會(huì)被覆蓋,因此,通過重寫getItemOffsets,通過其中的outRect來指定留出的空隙:

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            //如果是縱向排列,那么要在itemView的下方留出一個(gè)下邊界
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            //如果是橫向排列,那么要在itemView的右方留出一個(gè)右邊界
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

四、自定義ItemDecoration

下面,我們參考上面的寫法,寫一個(gè)簡單的GridLayoutManager的分割線:

public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
    private Drawable mDivider;
    private final Rect mBounds = new Rect();

    public GridDividerItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    public void setDrawable(@NonNull Drawable drawable) {
        mDivider = drawable;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawDivider(c, parent);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
    }

    private void drawDivider(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(view, mBounds);
            mDivider.setBounds(mBounds.right - mDivider.getIntrinsicWidth(), mBounds.top, mBounds.right, mBounds.bottom);
            mDivider.draw(canvas);
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDivider.getIntrinsicHeight(), mBounds.right , mBounds.bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
   }
}

最終的效果為:


這里我們?yōu)榱搜菔痉奖?,沒有考慮最后一列或者最后一行沒有分割線的情況,這篇文章寫的比較好:Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件

五、總結(jié)

ItemDecoration的使用并不難,大多數(shù)情況下就只需要重寫onDrawonDrawOver中的一個(gè);如果需要在Item之間添加間隔,那么要重寫getItemOffsets并理解outRect的含義,假如不需要添加間隔,那么不需要重寫該方法。


更多文章,歡迎訪問我的 Android 知識(shí)梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容