RecyclerView 知識梳理(4) ItemDecoration

1、概述

經過ItemDecoration,能夠給RecyclerView或者RecyclerView中的每一個Item添加額外的裝飾效果,最經常使用的就是用來爲Item之間添加分割線,今天,咱們就來一塊兒學習有關的知識:android

  • API
  • DividerItemDecoration解析
  • 自定義ItemDecoration

2、API介紹

當咱們實現本身的ItemDecoration時,須要繼承於ItemDecoration,並根據須要實現如下三個方法:canvas

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

  • canvasRecyclerViewcanvas
  • parentRecyclerView實例
  • StateRecyclerView當前的狀態,值包括START/LAYOUT/ANIMATION

全部在這個方法中的繪製操做,將會在itemViews被繪製以前執行,所以,它會顯示在itemView之下。bash

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

2.1方法相似,區別在於它繪製在itemViews之上。ide

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

經過outRect,能夠設置item之間的間隔,間隔區域的大小就是outRect所指定的範圍,view就是對應位置的itemView,其它的參數解釋和上面相同。學習

3、DividerItemDecoration解析

3.1 使用方法

上面咱們解釋了須要重寫的方法以及其中參數的含義,下面,咱們經過官方自帶的DividerItemDecoration,來進一步加深對這些方法的認識。 DividerItemDecoration是爲LinearLayoutManager提供的分割線,在建立它的時候,須要指定ORIENTATION,這個方向應當和LinearLayoutManager的方向相同。ui

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);
    }
複製代碼

最終展現的效果爲: this

3.2 源碼解析

3.2.1 繪製

DividerItemDecoration重寫了基類當中的onDraw方法,也就是說這個分割線是在itemView以前繪製的:spa

@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分割線:.net

@SuppressLint("NewApi")
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        //首先保存畫布
        canvas.save();
        final int left;
        final int right;
        //肯定左右邊界的範圍,若是RecyclerView不容許子View繪製在Padding內,那麼這個範圍爲去掉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的範圍,這個範圍包括了margin和offset,它們被保存在mBounds當中
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            //須要考慮translationY和translationY
            final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
            //因爲是垂直排列的,所以上邊界等於下邊界減去分割線的高度.
            final int top = bottom - mDivider.getIntrinsicHeight();
            //設置divider和範圍
            mDivider.setBounds(left, top, right, bottom);
            //繪製.
            mDivider.draw(canvas);
        }
        //回覆畫布.
        canvas.restore();
    }
複製代碼

整個過程分爲三步:rest

  • 肯定子ViewRecyclerView中的繪製範圍
  • 肯定每一個子View的範圍
  • 肯定mDivider的繪製範圍

下圖就是最終計算的結果:

橫向排列的 RecyclerView列表和上面的原理是相同的,區別就在於計算 mDivider.setBounds的計算:

//....
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的範圍內,那麼因爲咱們是先繪製divider,再繪製itemView的內容的,那麼它就會被覆蓋,所以,經過重寫getItemOffsets,經過其中的outRect來指定留出的空隙:

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

4、自定義ItemDecoration

下面,咱們參考上面的寫法,寫一個簡單的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();
   }
}
複製代碼

最終的效果爲:

這裏咱們爲了演示方便,沒有考慮最後一列或者最後一行沒有分割線的狀況,這篇文章寫的比較好: Android RecyclerView 使用徹底解析 體驗藝術般的控件

5、總結

ItemDecoration的使用並不難,大多數狀況下就只須要重寫onDrawonDrawOver中的一個;若是須要在Item之間添加間隔,那麼要重寫getItemOffsets並理解outRect的含義,假如不須要添加間隔,那麼不須要重寫該方法。


更多文章,歡迎訪問個人 Android 知識梳理系列:

相關文章
相關標籤/搜索