如何使用GridLayoutManager与RecyclerView设置列间距? 在我的布局中设置空白/填充没有效果。


当前回答

我最后用GridLayoutManager和HeaderView为我的RecyclerView做了这样的事情。

在下面的代码中,我在每个项目之间设置了4dp空间(每个项目周围都是2dp,整个RecyclerView周围是2dp填充)。

在layout.xml:

<android.support.v7.widget.RecyclerView
    android:id="@+id/recycleview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="2dp" />

在你的片段/活动中:

GridLayoutManager manager = new GridLayoutManager(getContext(), 3);
recyclerView.setLayoutManager(manager);
int spacingInPixels = Utils.dpToPx(2);
recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

创建SpaceItemDecoration.java:

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpacing;

    public SpacesItemDecoration(int spacing) {
        mSpacing = spacing;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
        outRect.left = mSpacing;
        outRect.top = mSpacing;
        outRect.right = mSpacing;
        outRect.bottom = mSpacing;
    }
}

在Utils.java:

public static int dpToPx(final float dp) {
    return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

其他回答

如果你想在所有设备中固定你的RecyclerView项的大小。你可以这样做

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpanCount;
    private float mItemSize;

    public GridSpacingItemDecoration(int spanCount, int itemSize) {
        this.mSpanCount = spanCount;
        mItemSize = itemSize;
    }

    @Override
    public void getItemOffsets(final Rect outRect, final View view, RecyclerView parent,
            RecyclerView.State state) {
        final int position = parent.getChildLayoutPosition(view);
        final int column = position % mSpanCount;
        final int parentWidth = parent.getWidth();
        int spacing = (int) (parentWidth - (mItemSize * mSpanCount)) / (mSpanCount + 1);
        outRect.left = spacing - column * spacing / mSpanCount;
        outRect.right = (column + 1) * spacing / mSpanCount;

        if (position < mSpanCount) {
            outRect.top = spacing;
        }
        outRect.bottom = spacing;
    }
}

recyclerview_item.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/recycler_view_item_width" 
    ...
    >
    ...
</LinearLayout>

dimens.xml

 <dimen name="recycler_view_item_width">60dp</dimen>

活动

int numberOfColumns = 3;
mRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns));
mRecyclerView.setAdapter(...);
mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3,
        getResources().getDimensionPixelSize(R.dimen.recycler_view_item_width)));

RecyclerViews支持ItemDecoration的概念:围绕每个元素的特殊偏移和绘图。从这个答案中可以看出,你可以使用

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
  private int space;

  public SpacesItemDecoration(int space) {
    this.space = space;
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, 
      RecyclerView parent, RecyclerView.State state) {
    outRect.left = space;
    outRect.right = space;
    outRect.bottom = space;

    // Add top margin only for the first item to avoid double space between items
    if (parent.getChildLayoutPosition(view) == 0) {
        outRect.top = space;
    } else {
        outRect.top = 0;
    }
  }
}

然后通过

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

试试这个。它会照顾到周围的等距。适用于列表,网格和StaggeredGrid。

编辑

更新后的代码应该可以处理大多数带有跨度、方向等的角落情况。 注意,如果在GridLayoutManager中使用setSpanSizeLookup(),出于性能考虑,建议设置setSpanIndexCacheEnabled()。

注意,似乎与StaggeredGrid,似乎有一个错误的索引的孩子变得古怪和难以跟踪,所以下面的代码可能不会很好地与StaggeredGridLayoutManager。

public class ListSpacingDecoration extends RecyclerView.ItemDecoration {

  private static final int VERTICAL = OrientationHelper.VERTICAL;

  private int orientation = -1;
  private int spanCount = -1;
  private int spacing;
  private int halfSpacing;


  public ListSpacingDecoration(Context context, @DimenRes int spacingDimen) {

    spacing = context.getResources().getDimensionPixelSize(spacingDimen);
    halfSpacing = spacing / 2;
  }

  public ListSpacingDecoration(int spacingPx) {

    spacing = spacingPx;
    halfSpacing = spacing / 2;
  }

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

    super.getItemOffsets(outRect, view, parent, state);

    if (orientation == -1) {
        orientation = getOrientation(parent);
    }

    if (spanCount == -1) {
        spanCount = getTotalSpan(parent);
    }

    int childCount = parent.getLayoutManager().getItemCount();
    int childIndex = parent.getChildAdapterPosition(view);

    int itemSpanSize = getItemSpanSize(parent, childIndex);
    int spanIndex = getItemSpanIndex(parent, childIndex);

    /* INVALID SPAN */
    if (spanCount < 1) return;

    setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex);
  }

  protected void setSpacings(Rect outRect, RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    outRect.top = halfSpacing;
    outRect.bottom = halfSpacing;
    outRect.left = halfSpacing;
    outRect.right = halfSpacing;

    if (isTopEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.top = spacing;
    }

    if (isLeftEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.left = spacing;
    }

    if (isRightEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.right = spacing;
    }

    if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.bottom = spacing;
    }
  }

  @SuppressWarnings("all")
  protected int getTotalSpan(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanSize(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return 1;
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanIndex(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return childIndex % spanCount;
    } else if (mgr instanceof LinearLayoutManager) {
        return 0;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getOrientation(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof LinearLayoutManager) {
        return ((LinearLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getOrientation();
    }

    return VERTICAL;
  }

  protected boolean isLeftEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return spanIndex == 0;

    } else {

        return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);
    }
  }

  protected boolean isRightEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return (spanIndex + itemSpanSize) == spanCount;

    } else {

        return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);
    }
  }

  protected boolean isTopEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);

    } else {

        return spanIndex == 0;
    }
  }

  protected boolean isBottomEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);

    } else {

        return (spanIndex + itemSpanSize) == spanCount;
    }
  }

  protected boolean isFirstItemEdgeValid(boolean isOneOfFirstItems, RecyclerView parent, int childIndex) {

    int totalSpanArea = 0;
    if (isOneOfFirstItems) {
        for (int i = childIndex; i >= 0; i--) {
            totalSpanArea = totalSpanArea + getItemSpanSize(parent, i);
        }
    }

    return isOneOfFirstItems && totalSpanArea <= spanCount;
  }

  protected boolean isLastItemEdgeValid(boolean isOneOfLastItems, RecyclerView parent, int childCount, int childIndex, int spanIndex) {

    int totalSpanRemaining = 0;
    if (isOneOfLastItems) {
        for (int i = childIndex; i < childCount; i++) {
            totalSpanRemaining = totalSpanRemaining + getItemSpanSize(parent, i);
        }
    }

    return isOneOfLastItems && (totalSpanRemaining <= spanCount - spanIndex);
  }
}

希望能有所帮助。

这就是最后对我有用的方法:

binding.rows.addItemDecoration(object: RecyclerView.ItemDecoration() {
    val px = resources.getDimensionPixelSize(R.dimen.grid_spacing)
    val spanCount = 2

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val index = parent.getChildLayoutPosition(view)
        val isLeft = (index % spanCount == 0)
        outRect.set(
            if (isLeft) px else px/2,
            0,
            if (isLeft) px/2 else px,
            px
        )
    }
})

因为我只有2列(val spanCount = 2),我可以只做isLeft。如果有> 2列,那么我也需要一个isMiddle,两边的值都是px/2。

我希望有一种方法来获得应用程序:spanCount从直接从RecyclerView,但我不相信有。

感谢edwardaa的回答https://stackoverflow.com/a/30701422/2227031

另一点需要注意的是:

如果total spacing和total itemWidth不等于屏幕宽度,您还需要调整itemWidth,例如,在适配器onBindViewHolder方法

Utils.init(_mActivity);
int width = 0;
if (includeEdge) {
    width = ScreenUtils.getScreenWidth() - spacing * (spanCount + 1);
} else {
    width = ScreenUtils.getScreenWidth() - spacing * (spanCount - 1);
}
int itemWidth = width / spanCount;

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) holder.imageViewAvatar.getLayoutParams();
// suppose the width and height are the same
layoutParams.width = itemWidth;
layoutParams.height = itemWidth;
holder.imageViewAvatar.setLayoutParams(layoutParams);