RecyclerView GridLayoutManager 等分間距

RecyclerView 表格實現

RecyclerView 配合GridLayoutManager 可以實現類似表格的樣式,爲了實現均分,adapter 的佈局寬度改爲匹配父元素,即 android:layout_width=“match_parent” 。

RecyclerView rvPhotoAlbums = findViewById(R.id.rv_photoAlbums_content);
rvPhotoAlbums.setLayoutManager(new GridLayoutManager(this, 4));
rvPhotoAlbums.setAdapter(new PhotoAlbumsAdapter());

效果圖如下:

添加間距 ItemDecoration

ItemDecoration 源碼分析

androidx.recyclerview.widget.RecyclerView#addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration)。
先簡單看下 ItemDecoration 裏的方法:

public abstract static class ItemDecoration {
    // ....省略其他方法介紹.....

    /**
     * Retrieve any offsets for the given item. Each field of <code>outRect</code> 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 view的偏移量,用outRect 表示。outRect的每個字段分別表示不同方向的偏移量,類似於填充或邊距。
     * 默認設置爲0,即沒有間距。
     *
     * <p>
     * If this ItemDecoration does not affect the positioning of item views, it should set
     * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
     * before returning.
     * 如果不想影響item view 的位置,需要置outRect 的left, top, right, bottom 爲0
     *
     * <p>
     * If you need to access Adapter for additional data, you can call
     * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
     * View.
     *
     * @param outRect Rect to receive the output.
     * @param view    The child view to decorate
     * @param parent  RecyclerView this ItemDecoration is decorating
     * @param state   The current state of RecyclerView.
     */
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
            @NonNull RecyclerView parent, @NonNull State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
}

通過代碼註釋我們知道,可以通過繼承 androidx.recyclerview.widget.RecyclerView.ItemDecoration 類處理間距。

自定義ItemDecoration

public class GridSpaceItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpanCount;//橫條目數量
    private int mRowSpacing;//行間距
    private int mColumnSpacing;// 列間距

    /**
     * @param spanCount     列數
     * @param rowSpacing    行間距
     * @param columnSpacing 列間距
     */
    public GridSpaceItemDecoration(int spanCount, int rowSpacing, int columnSpacing) {
        this.mSpanCount = spanCount;
        this.mRowSpacing = rowSpacing;
        this.mColumnSpacing = columnSpacing;
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // 獲取view 在adapter中的位置。
        int column = position % mSpanCount; // view 所在的列

        // 列間距
        outRect.left = mColumnSpacing;

        // 如果position > 行數,說明不是在第一行,則不指定行高,其他行的上間距爲 top=mRowSpacing
        if (position >= mSpanCount) {
            outRect.top = mRowSpacing; // item top
        }
    }
}

添加ItemDecoration

// 添加間距
rvPhotoAlbums.addItemDecoration(new GridSpaceItemDecoration(4,
        DpPxSpTool.INSTANCE.dip2px(this, 30),
        DpPxSpTool.INSTANCE.dip2px(this, 20)));

ok,我們看下效果:

(注:爲了區分item view範圍,我用不同的顏色描邊來識別)
可以看出,item 整體偏右,這是因爲GridLayoutManager 已經給item 劃分了寬度,而我們在ItemDecoration 中給item view設置了左邊距,所以會出現整體偏右,內容擠壓的情況。

等分間距

實現思路

爲了達到同等間距,我們需要同時設置item 的左右邊距,並且使 左邊view 的邊距+ 右邊view 的左邊距 = 設置的列間距,從視覺效果上看起來是同等分間距。

先來梳理下等間距需要滿足的條件:

  1. 各個模塊的大小相等,即各列的left+right 值相等;
  2. 各列的間距相等,即前列的right + 後列的left = 列間距;

假設列間距爲10,爲了方便識別,我用字母代替所在列。
以2列爲例:
在這裏插入圖片描述
檢查條件1 :a.left +a.right = b.left+b.right = 5 結果:滿足
檢查條件2:a.right + b.left = 10 結果:滿足

以3列爲例:
在這裏插入圖片描述
檢查條件1 :a.left +a.right = b.left+b.right = c.left+c.right ≈ 6.66 結果:滿足
檢查條件2:a.right + b.left = b.right + c.left ≈ 10 結果:滿足

根據推演我們可以得出公式:
某列的left = 所在的列數 * (列間距 * (1 / 列數))
某列的right = 列間距 - 後列的left = 列間距 -(所在的列數+1) * (列間距 * (1 / 列數))
注:這裏用的所在列數爲從0開始

最終實現

/**
 * 描述 : RecyclerView GridLayoutManager 等間距。
 * <p>
 * 等間距需滿足兩個條件:
 * 1.各個模塊的大小相等,即 各列的left+right 值相等;
 * 2.各列的間距相等,即 前列的right + 後列的left = 列間距;
 * <p>
 * 在{@link #getItemOffsets(Rect, View, RecyclerView, RecyclerView.State)} 中針對 outRect 的left 和right 滿足這兩個條件即可
 * <p>
 * 作者 : shiguotao
 * 版本 : V1
 * 創建時間 : 2020/3/19 4:54 PM
 */
public class GridSpaceItemDecoration extends RecyclerView.ItemDecoration {

    private final String TAG = "GridSpaceItemDecoration";

    private int mSpanCount;//橫條目數量
    private int mRowSpacing;//行間距
    private int mColumnSpacing;// 列間距

    /**
     * @param spanCount     列數
     * @param rowSpacing    行間距
     * @param columnSpacing 列間距
     */
    public GridSpaceItemDecoration(int spanCount, int rowSpacing, int columnSpacing) {
        this.mSpanCount = spanCount;
        this.mRowSpacing = rowSpacing;
        this.mColumnSpacing = columnSpacing;
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // 獲取view 在adapter中的位置。
        int column = position % mSpanCount; // view 所在的列

        outRect.left = column * mColumnSpacing / mSpanCount; // column * (列間距 * (1f / 列數))
        outRect.right = mColumnSpacing - (column + 1) * mColumnSpacing / mSpanCount; // 列間距 - (column + 1) * (列間距 * (1f /列數))

        Log.e(TAG, "position:" + position
                + "    columnIndex: " + column
                + "    left,right ->" + outRect.left + "," + outRect.right);

        // 如果position > 行數,說明不是在第一行,則不指定行高,其他行的上間距爲 top=mRowSpacing
        if (position >= mSpanCount) {
            outRect.top = mRowSpacing; // item top
        }
    }
}

效果:

打完,收工!

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