RecyclerView GridLayout 设置等宽 item 间距的正确姿势

Posted by afon on February 22, 2017

给 RecyclerView GirdLayout 的各个 item 设置一个分隔间距并不难,只需自定义一个 ItemDecoration,重写 getItemOffsets 方法设置合适的间距即可,比如可以给 left 和 right 各设置 1/2 个间距,就可以达到目的了。但有时我们需要做一些精细化的调整,比如第一列和最后一列不要显示间距,然而当我们根据 item 位置判断,给第一列的 item left 设置为 0,给最后一列的 item right 设置为 0,会发现第一列和最后一列的 item 宽度会比中间的 item 稍宽一点。

这里有一个高票答案:

http://stackoverflow.com/questions/29146781/decorating-recyclerview-with-gridlayoutmanager-to-display-divider-between-item

但在列大于 5、6 列时,也会出现 item 宽度不等宽的问题,本文将解释如何解决这一问题。

要满足 item 的宽度等宽,首先要计算合适的间距:每个 item 的 left + right 要相等,前一个 item 的 right 加上后一个 item 的 left 等于间距,因此等宽的间距规则可以用下图表示:

用代码计算如下:

    /**
     * 自定义分割线
     * 注意:如果没有设置分割线大小(setDividerSize),默认不画分割线,只有 item 之间的间距
     *
     * @param colCount 列数量
     * @param colSeparateSize item 水平间距
     * @param rowSeparateSize item 垂直间距
     */
    public RecyclerViewGridDivider(int colCount, int colSeparateSize, int rowSeparateSize) {
        if (colCount <= 1) {
            throw new IllegalArgumentException("wrong args! colCount must larger than 1");
        }
        this.colCount = colCount;
        this.colSeparateSize = colSeparateSize;
        this.rowSeparateSize = rowSeparateSize;

        // 预分配 getItemOffsets 方法内设置的 left, right 间距。如果不这样计算,水平方向上的各个 item 的宽度将不相等
        float inset = colSeparateSize / colCount;
        insetBuffers = new int[colCount * 2];
        for (int i = 0 ; i < colCount ; i++) {
            // 计算规则:每个 item 的 left + right 要相等,前一个 item 的 right 加上后一个 item 的 left 等于 colSeparateSize
            insetBuffers[i * 2 + 0] = (int) (inset * i); // left
            insetBuffers[i * 2 + 1] = (int) (inset * (colCount - 1 - i)); // right
        }
    }

最后,重写 getItemOffsets 就可以直接拿预计算的 insetBuffers 里的值:

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

        final int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();

        if (itemPosition < colCount) {
            outRect.top = 0;
        } else {
            outRect.top = rowSeparateSize;
        }

        outRect.bottom = 0;

        int i = itemPosition % colCount;
        outRect.left = insetBuffers[i * 2 + 0];
        outRect.right = insetBuffers[i * 2 + 1];
    }

在代码里面我没有设置第一列的左间距、最后一列的右间距、第一行的上间距、最后一行的下间距,如果需要,在 xml 布局文件中添加:

        android:padding="10dp"
        android:clipToPadding="false"
        android:clipChildren="false"
        android:scrollbarStyle="outsideOverlay"
        android:scrollbars="vertical"

演示效果如下:

Github地址:

https://github.com/wordplat/RecyclerViewEasyDivider