RecyclerView addItemDecoration 的妙用 - item 間距平均分佈和添加分割線

前言

RecyclerView,在開發當中使用非常頻繁的一個控件,今天,主要講解以下兩個問題

  • 添加分割線
  • item 間距的平均分佈

addItemDecoration 方法簡介

我們先來看一下 addItemDecoration 方法

官網鏈接

Add an RecyclerView.ItemDecoration to this RecyclerView. Item decorations can affect both measurement and drawing of individual item views.

Item decorations are ordered. Decorations placed earlier in the list will be run/queried/drawn first for their effects on item views. Padding added to views will be nested; a padding added by an earlier decoration will mean further item decorations in the list will be asked to draw/pad within the previous decoration's given area.

簡單來來說,ItemDecoration 是 itemView 的裝飾,可以影響 itemView 的 measurement 和 draw。

ItemDecoration 主要有幾個方法

getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

Retrieve any offsets for the given item. Each field of outRect specifies the number of pixels that the item view should be inset by, similar to padding or margin. The default implementation sets the bounds of outRect to 0 and returns.

可以影響 item 的大小,類似於在 item 中設置 padding 和 margin。

void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state)

Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn before the item views are drawn, and will thus appear underneath the views.

在 itemView 之前繪製

void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)

Draw any appropriate decorations into the Canvas supplied to the RecyclerView. Any content drawn by this method will be drawn after the item views are drawn and will thus appear over the views.

在 itemView 之後繪製


添加分割線

效果圖如下

RecyclerViewDivider,已支持以下功能

  1. 自定義分割線,設置 drawable
  2. 設置分割線高度,顏色
  3. 設置分割線距離屏幕左邊,右邊的距離
  4. 設置是否顯示最後一條分割線

詳情代碼見 RecyclerViewSample

實現思路

我們知道 RecyclerView 沒有像之前 ListView 提供 divider 屬性,設置分割線的話有挺多人在 itemView 的佈局裏面加個 1dp 左右的 view,根據業務場景設置是否可見。這是其中的一種方法,但其實,我們也可以使用 recyclerView.addItemDecoration() 來實現,主要需要重寫 getItemOffsets 和 onDraw 方法

思路很簡單

  1. 重寫 getItemOffsets,加上 divider 的高度,影響 itemView 的最終 size
  2. 在 onDraw 方法,根據 LinearLayoutManager 的方向分別繪製分割線
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    outRect.set(0, 0, 0, mDividerHeight);
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    if (mOrientation == LinearLayoutManager.VERTICAL) {
        drawVerticalDivider(c, parent);
    } else {
        drawHorizontalDivider(c, parent);
    }
}
//Draw item dividing line
private void drawVerticalDivider(Canvas canvas, RecyclerView parent) {
    // mLeftOffset 爲自己設置的左邊偏移量
    final int left = parent.getPaddingLeft() + mLeftOffset;
    // mRightOffset 爲設置的右邊偏移量
    final int right = parent.getMeasuredWidth() - parent.getPaddingRight() + mRightOffset;
    final int childSize = parent.getChildCount();

    if (childSize <= 0) {
        return;
    }

    // 從第一個 item 開始繪製
    int first = mStart;
    // 到第幾個 item 繪製結束
    int last = childSize - mEnd - (mIsShowLastDivider ? 0 : 1);
    Log.d(TAG, " last = " + last + " childSize =" + childSize + "left = " + left);

    if (last <= 0) {
        return;
    }

    for (int i = first; i < last; i++) {
        drawableVerticalDivider(canvas, parent, left, right, i, mDividerHeight);
    }

}

private void drawableVerticalDivider(Canvas canvas, RecyclerView parent, int left, int right, int i, int dividerHeight) {
    final View child = parent.getChildAt(i);

    if (child == null) {
        return;
    }

    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
    final int top = child.getBottom() + layoutParams.bottomMargin;
    final int bottom = top + dividerHeight;

    // 適配 drawable
    if (mDivider != null) {
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(canvas);
    }

    // 適配分割線
    if (mPaint != null) {
        canvas.drawRect(left, top, right, bottom, mPaint);
    }
}



Item 間距平均分佈

針對 GridLayoutManager 的

在 Android 開發當中,我們經常會看到這樣的界面,

一般來說,可能有以下幾種需求:

  1. 要求第一列和最後一列距離屏幕的距離 A 是固定的,其餘每個 item 之間的距離 B 也是固定的(但 A 不等於 B
  2. 要求第一列和最後一列距離屏幕的距離 A 是固定的,item 的大小是固定的,其餘每個 item 之間的距離跟隨分辨率的大小變化
  3. 第一行距離頂部的距離可以設置,最後一行距離底部的距離可以設置

思路分析

首先,我們知道,對於 GridLayoutmanager ,當我們設置的 spancount 爲 3 的時候,那麼每個 item
的最大寬度爲 itemMaxW = recycylerW / spancount = recycylerW / 3.

假設我們 spancount 爲 3,那麼在不設置 itemDercation 的情況下它的分佈是這樣的,可以看到第一列與最後一行的距離是不一樣的

而加上 itemDercation 之後,我們所看到的 item 是這樣的,因此,我們可以分別對每個 item 的 ouctRect 進行處理

核心代碼如下:

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

    int top = 0;
    int left;
    int right;
    int bottom;

    int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
    mSpanCount = getSpanCount(parent);
    int childCount = parent.getAdapter().getItemCount();

    // 屏幕寬度-View的寬度*spanCount 得到屏幕剩餘空間
    int maxDividerWidth = getMaxDividerWidth(view);
    int spaceWidth = mFirstAndLastColumnW;//首尾兩列與父佈局之間的間隔
    // 除去首尾兩列,item與item之間的距離
    int eachItemWidth = maxDividerWidth / mSpanCount;
    int dividerItemWidth = (maxDividerWidth - 2 * spaceWidth) / (mSpanCount - 1);//item與item之間的距離

    left = itemPosition % mSpanCount * (dividerItemWidth - eachItemWidth) + spaceWidth;
    right = eachItemWidth - left;
    bottom = dividerItemWidth;

    // 首行
    if (mFirstRowTopMargin > 0 && isFirstRow(parent, itemPosition, mSpanCount, childCount)) {
        top = mFirstRowTopMargin;
    }

    //最後一行
    if (isLastRow(parent, itemPosition, mSpanCount, childCount)) {
        if (mLastRowBottomMargin < 0) {
            bottom = 0;
        } else {
            bottom = mLastRowBottomMargin;
        }
    }

    Log.i(TAG, "getItemOffsets: dividerItemWidth =" + dividerItemWidth + "eachItemWidth = " + eachItemWidth);

    Log.i(TAG, "getItemOffsets: itemPosition =" + itemPosition + " left = " + left + " top = " + top
            + " right = " + right + " bottom = " + bottom);

    outRect.set(left, top, right, bottom);
}


在代碼中使用

mRecyclerView.setLayoutManager(layoutManager);
int firstAndLastColumnW = DisplayUtils.dp2px(this, 15);
int firstRowTopMargin = DisplayUtils.dp2px(this, 15);
GridDividerItemDecoration gridDividerItemDecoration =
        new GridDividerItemDecoration(this, firstAndLastColumnW, firstRowTopMargin, firstRowTopMargin);
gridDividerItemDecoration.setFirstRowTopMargin(firstRowTopMargin);
gridDividerItemDecoration.setLastRowBottomMargin(firstRowTopMargin);
mRecyclerView.addItemDecoration(gridDividerItemDecoration);
List<Integer> imageList = DataUtils.produceImageList(30);
ImageAdapter imageAdapter = new ImageAdapter(this, imageList);

mRecyclerView.setAdapter(imageAdapter);

針對橫向的 LinearlayoutManager

@Override
public void getItemOffsets(Rect outRect, View view,
                           RecyclerView parent, RecyclerView.State state) {
    int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
    int childCount = parent.getAdapter().getItemCount();

    if (itemPosition == 0) {
        outRect.left = firstAndlastRect.left;
        outRect.right = firstAndlastRect.right;
        outRect.bottom = firstAndlastRect.bottom;
        outRect.top = firstAndlastRect.top;
        return;
    }

    if (itemPosition == childCount - 1) {
        outRect.left = 0;
        outRect.right = firstAndlastRect.left;
        outRect.bottom = firstAndlastRect.bottom;
        outRect.top = firstAndlastRect.top;
        return;
    }

    setOutRect(outRect, space);
}

int j = DisplayUtils.dp2px(this, 15);
Rect firstAndLastRect = new Rect(j, i, i, 0);
HorizontalSpacesDecoration spacesDecoration = new HorizontalSpacesDecoration(rect, firstAndLastRect);
mRecyclerView.addItemDecoration(spacesDecoration);
List<Integer> integers = DataUtils.produceImageList(R.mipmap.ty1, 30);
ImageAdapter imageAdapter = new ImageAdapter(this, integers);
mRecyclerView.setAdapter(imageAdapter);

RecyclerViewSamplehttps://github.com/gdutxiaoxu/RecyclerViewSample

掃一掃,歡迎關注我的公衆號。如果你有好的文章,也歡迎你的投稿。

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