装饰 RecyclerView(使用 GridLayoutManager)以显示项目之间的分隔线
Decorating RecyclerView (with GridLayoutManager) to display divider between items
将 RecyclerView 装饰成这样的外观的最好和最简单的方法是什么?
这里的主要挑战是仅在项目之间设置分隔线,而不是在项目和 left/right 屏幕边框之间设置分隔线。
有什么想法吗?
我不知道你为什么需要它,但是 UI 使用 RecyclerView 装饰器很容易实现。
<!--Integer Value that number of column in RecyclerView-->
<integer name="photo_list_preview_columns">3</integer>
<!-- inter spacing between RecyclerView's Item-->
<dimen name="photos_list_spacing">10dp</dimen>
您可以根据需要更改photo_list_preview_columns和photos_list_spacing。
mRecylerView.addItemDecoration(new ItemDecorationAlbumColumns(
getResources().getDimensionPixelSize(R.dimen.photos_list_spacing),
getResources().getInteger(R.integer.photo_list_preview_columns)));
和装饰器(需要一些改造)
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class ItemDecorationAlbumColumns extends RecyclerView.ItemDecoration {
private int mSizeGridSpacingPx;
private int mGridSize;
private boolean mNeedLeftSpacing = false;
public ItemDecorationAlbumColumns(int gridSpacingPx, int gridSize) {
mSizeGridSpacingPx = gridSpacingPx;
mGridSize = gridSize;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
int padding = parent.getWidth() / mGridSize - frameWidth;
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
if (itemPosition < mGridSize) {
outRect.top = 0;
} else {
outRect.top = mSizeGridSpacingPx;
}
if (itemPosition % mGridSize == 0) {
outRect.left = 0;
outRect.right = padding;
mNeedLeftSpacing = true;
} else if ((itemPosition + 1) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.right = 0;
outRect.left = padding;
} else if (mNeedLeftSpacing) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx - padding;
if ((itemPosition + 2) % mGridSize == 0) {
outRect.right = mSizeGridSpacingPx - padding;
} else {
outRect.right = mSizeGridSpacingPx / 2;
}
} else if ((itemPosition + 2) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx - padding;
} else {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx / 2;
}
outRect.bottom = 0;
}
}
您可能得到了答案,但我仍在发布可以帮助其他人的解决方案。这可以通过传递方向用于垂直、水平列表或网格视图。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
public static final int GRID = 2;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST && orientation != GRID) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else if(mOrientation == HORIZONTAL_LIST){
drawHorizontal(c, parent);
} else {
drawVertical(c, parent);
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
if (parent.getChildCount() == 0) return;
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final View child = parent.getChildAt(0);
if (child.getHeight() == 0) return;
final RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
int bottom = top + mDivider.getIntrinsicHeight();
final int parentBottom = parent.getHeight() - parent.getPaddingBottom();
while (bottom < parentBottom) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
top += mDivider.getIntrinsicHeight() + params.topMargin + child.getHeight() + params.bottomMargin + mDivider.getIntrinsicHeight();
bottom = top + mDivider.getIntrinsicHeight();
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + params.rightMargin + mDivider.getIntrinsicHeight();
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else if(mOrientation == HORIZONTAL_LIST) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
}
}
@BindingAdapter({"bind:adapter"})
public static void bind(RecyclerView view, RecyclerView.Adapter<BaseViewHolder> adapter) {
view.setLayoutManager(new GridLayoutManager(view.getContext(), 3));
view.addItemDecoration(new SpacesItemDecorationGrid(view.getContext(), 4, 3));
view.setItemAnimator(new DefaultItemAnimator());
view.setAdapter(adapter);
}
public class SpacesItemDecorationGrid extends RecyclerView.ItemDecoration {
private int mSizeGridSpacingPx;
private int mGridSize;
private boolean mNeedLeftSpacing = false;
/**
* @param gridSpacingPx
* @param gridSize
*/
SpacesItemDecorationGrid(Context context, int gridSpacingPx, int gridSize) {
mSizeGridSpacingPx = (int) Util.convertDpToPixel(gridSpacingPx, context);
mGridSize = gridSize;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
int padding = parent.getWidth() / mGridSize - frameWidth;
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
int itemCount = parent.getAdapter().getItemCount() - mGridSize;
/* if (itemPosition < mGridSize) {
outRect.top = mSizeGridSpacingPx;
} else {
outRect.top = mSizeGridSpacingPx;
}*/
outRect.top = mSizeGridSpacingPx;
if (itemPosition % mGridSize == 0) {
outRect.left = mSizeGridSpacingPx;
outRect.right = padding;
mNeedLeftSpacing = true;
} else if ((itemPosition + 1) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.right = mSizeGridSpacingPx;
outRect.left = padding;
} else if (mNeedLeftSpacing) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx - padding;
if ((itemPosition + 2) % mGridSize == 0) {
outRect.right = mSizeGridSpacingPx - padding;
} else {
outRect.right = mSizeGridSpacingPx / 2;
}
} else if ((itemPosition + 2) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx - padding;
} else {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx / 2;
}
if (itemPosition > itemCount) {
outRect.bottom = mSizeGridSpacingPx;
} else {
outRect.bottom = 0;
}
}
}
另一个对我有用的简单解决方案。希望有用。
class GridItemDecorator(val context: Context, private val spacingDp: Int, private val mGridSize: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val resources = context.resources
val spacingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingDp.toFloat(), resources.displayMetrics)
val bit = if (spacingPx > mGridSize) Math.round(spacingPx / mGridSize) else 1
val itemPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition
outRect.top = if (itemPosition < mGridSize) 0 else bit * mGridSize
outRect.bottom = 0
val rowPosition = itemPosition % mGridSize
outRect.left = rowPosition * bit
outRect.right = (mGridSize - rowPosition - 1) * bit
}
}
下面是一个更简单、对用户更友好的实现:
public class MediaSpaceDecoration extends RecyclerView.ItemDecoration {
private final int spacing;
private final List<Integer> allowedViewTypes = Arrays.asList(
R.layout.item_image,
R.layout.item_blur);
public MediaSpaceDecoration(int spacing) {
this.spacing = spacing;
}
@Override
public void getItemOffsets(Rect outRect,
View view,
RecyclerView parent,
RecyclerView.State state) {
final int position = parent.getChildAdapterPosition(view);
if (!isMedia(parent, position)) {
return;
}
final int totalSpanCount = getTotalSpanCount(parent);
int spanSize = getItemSpanSize(parent, position);
if (totalSpanCount == spanSize) {
return;
}
outRect.top = isInTheFirstRow(position, totalSpanCount) ? 0 : spacing;
outRect.left = isFirstInRow(position, totalSpanCount) ? 0 : spacing / 2;
outRect.right = isLastInRow(position, totalSpanCount) ? 0 : spacing / 2;
outRect.bottom = 0; // don't need
}
private boolean isInTheFirstRow(int position, int spanCount) {
return position < spanCount;
}
private boolean isFirstInRow(int position, int spanCount) {
return position % spanCount == 0;
}
private boolean isLastInRow(int position, int spanCount) {
return isFirstInRow(position + 1, spanCount);
}
private int getTotalSpanCount(RecyclerView parent) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager
? ((GridLayoutManager) layoutManager).getSpanCount()
: 1;
}
private int getItemSpanSize(RecyclerView parent, int position) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager
? ((GridLayoutManager) layoutManager).getSpanSizeLookup().getSpanSize(position)
: 1;
}
private boolean isMedia(RecyclerView parent, int viewPosition) {
final RecyclerView.Adapter adapter = parent.getAdapter();
final int viewType = adapter.getItemViewType(viewPosition);
return allowedViewTypes.contains(viewType);
}
}
我也在设置 outRect
之前进行检查,因为每个 viewType
我都有不同的 spanSize
,我需要为 [ 添加一个额外的中间 space =14=]。您可以轻松删除该验证,代码会更简单。对我来说看起来像这样:
这是我在 Kotlin 中的实现。我修改了其他答案的代码,以便也为完整的项目显示分隔符。
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class GridDividerItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val totalSpanCount = getTotalSpanCount(parent)
val spanSize = getItemSpanSize(parent, position)
outRect.top = if (isInTheFirstRow(position, totalSpanCount)) 0 else spacing
outRect.left = if (isFirstInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.right = if (isLastInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.bottom = 0
}
private fun isInTheFirstRow(position: Int, totalSpanCount: Int): Boolean =
position < totalSpanCount
private fun isFirstInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
if (totalSpanCount != spanSize) {
position % totalSpanCount == 0
} else true
private fun isLastInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
isFirstInRow(position + 1, totalSpanCount, spanSize)
private fun getTotalSpanCount(parent: RecyclerView): Int =
(parent.layoutManager as? GridLayoutManager)?.spanCount ?: 1
private fun getItemSpanSize(parent: RecyclerView, position: Int): Int =
(parent.layoutManager as? GridLayoutManager)?.spanSizeLookup?.getSpanSize(position) ?: 1
}
结果:
下面是我的自定义 class,它允许 Kotlin 中的网格单元格之间的间距相等:
class GridItemOffsetDecoration(private val spanCount: Int, private var mItemOffset: Int) : ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
if (position < spanCount) {
if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset, mItemOffset / 2, mItemOffset / 2)
} else { // right grid
outRect.set(mItemOffset / 2, mItemOffset, 0, mItemOffset / 2)
}
} else if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset / 2)
} else if (position % 2 == 1) { // right grid
outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset / 2)
} else {
if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset)
} else { // right grid
outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset)
}
}
}
}
并将其添加为 RecyclerView 中的项目装饰器,添加以下行:
/*spanCount is the number of grids, for instance, (2 = 2*2 grid, 3 = 3*3)*/
binding.rvActiveChallenges.addItemDecoration(GridItemOffsetDecoration(2, resources.getDimensionPixelSize(R.dimen._10dp)))
这是我的版本。基于 但经过改进以处理包含三个或更多图像的网格并使用 dp 单位进行间距。 (他的版本不提供等尺寸 images/spacing 超过 2 张图片。)
注意:计算每个图像间距的逻辑比仅将间距除以二(每个图像减半)更复杂,大多数答案都没有考虑到这一点..
/**
* Class to add INTERNAL SPACING to a grid of items. Only works for a grid with 3 columns or more.
*/
class PhotoSpaceDecoration extends RecyclerView.ItemDecoration {
private final int spacingWidthPx;
/**
* Initialise with the with of the spacer in dp
*
* @param spacingWidthDp this will be divided between elements and applied as a space on each side
* NB: for proper alignment this must be divisible by 2 and by the number of columns
*/
public PhotoSpaceDecoration(Context context, int spacingWidthDp) {
// Convert DP to pixels
this.spacingWidthPx = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingWidthDp,
context.getResources().getDisplayMetrics());
}
/**
* @param index a 0 indexed value of the current item
* @param numberOfColumns
* @return a 0 indexed Point with the x & y location of the item in the grid
*/
private Point getItemXY(int index, int numberOfColumns) {
int x = index % numberOfColumns;
int y = index / numberOfColumns; // NB: integer division
return new Point(x, y);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final int position = parent.getChildAdapterPosition(view);
final int columns = getTotalSpanCount(parent);
final int rows = (int) Math.ceil(parent.getChildCount() / (double) columns); // NB: NOT integer division
int spanSize = getItemSpanSize(parent, position);
if (columns == spanSize) {
return;
}
Point point = getItemXY(position, columns);
int firstMargin = spacingWidthPx * (columns - 1) / columns;
int secondMargin = spacingWidthPx - firstMargin;
int middleMargin = spacingWidthPx / 2;
if (point.x == 0) { // first column
outRect.left = 0;
outRect.right = firstMargin;
} else if (point.x == 1) { // second column
outRect.left = secondMargin;
outRect.right = rows > 3 ? middleMargin : secondMargin;
} else if (point.x - columns == -2) { // penultimate column
outRect.left = rows > 3 ? middleMargin : secondMargin;
outRect.right = secondMargin;
} else if (point.x - columns == -1) { // last column
outRect.left = firstMargin;
outRect.right = 0;
} else { // middle columns
outRect.left = middleMargin;
outRect.right = middleMargin;
}
if (point.y == 0) { // first row
outRect.top = 0;
outRect.bottom = firstMargin;
} else if (point.y == 1) { // second row
outRect.top = secondMargin;
outRect.bottom = rows > 3 ? middleMargin : secondMargin;
} else if (point.y - rows == -2) { // penultimate row
outRect.top = rows > 3 ? middleMargin : secondMargin;
outRect.bottom = secondMargin;
} else if (point.y - rows == -1) { // last row
outRect.top = firstMargin;
outRect.bottom = 0;
} else { // middle rows
outRect.top = middleMargin;
outRect.bottom = middleMargin;
}
}
private int getTotalSpanCount(RecyclerView parent) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanCount() : 1;
}
private int getItemSpanSize(RecyclerView parent, int position) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanSizeLookup()
.getSpanSize(
position) : 1;
}
}
这适用于 Activity.onCreate()
的回收站视图,如下所示
photosRecyclerView.addItemDecoration(new PhotoSpaceDecoration(this, 6));
示例:
如果你header使用这个。
要隐藏 header 的分隔符设置 skipHeaderDivider=false
,否则设置 true
。
class GridDividerItemDecoration : ItemDecoration() {
var skipHeaderDivider = true
private var divider: Drawable? = null
private val bounds = Rect()
private var spacing = 0
fun setDrawable(drawable: Drawable) {
divider = drawable
divider?.intrinsicHeight?.let { spacing = it }
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
canvas.save()
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
parent.layoutManager?.getDecoratedBoundsWithMargins(child, bounds)
val right: Int = bounds.right + child.translationX.roundToInt()
val left: Int = bounds.left - child.translationX.roundToInt()
val bottom: Int = bounds.bottom + child.translationY.roundToInt()
val top: Int = bounds.top - child.translationY.roundToInt()
divider?.setBounds(left, top, right, bottom)
divider?.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val gridLayoutManager = parent.layoutManager as? GridLayoutManager ?: return
val position = gridLayoutManager.getPosition(view)
if (position < 0) return
val spanCount = gridLayoutManager.spanCount
val positionalSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position)
if (skipHeaderDivider && positionalSpanSize == spanCount) return
val itemCount = gridLayoutManager.itemCount
val onBottom = position >= itemCount - spanCount
var nextHeader = false
run loop@{
for (i in 1..spanCount) {
val nextSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position + i)
if (nextSpanSize == spanCount) {
nextHeader = true
return@loop
}
}
}
outRect.top = spacing
outRect.left = 0
outRect.right = spacing
outRect.bottom = if (nextHeader || onBottom) spacing else 0
}
}
你可以试试我的解决方案。如果您想要列表视图或网格视图,这很灵活。
class GridItemOffsetDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val totalSpanCount = getTotalSpanCount(parent)
val spanSize = getItemSpanSize(parent, position)
// if list view span == 1
if (totalSpanCount == 1) {
if (position == 0) {
outRect.top = 10.dp
} else {
outRect.top = spacing
}
outRect.left = spacing
outRect.right = spacing
if (position == parent.adapter?.itemCount?.minus(1)) {
outRect.bottom = spacing
}
} else { // if grid view span >= 2
outRect.top = if (isInTheFirstRow(position, totalSpanCount)) 0 else spacing
outRect.left = if (isFirstInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.right = if (isLastInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.bottom = 0
when {
position % 2 == 0 -> {
outRect.left = spacing
}
else -> {
outRect.right = spacing
}
}
outRect.top = 10.dp
if (position >= parent.adapter?.itemCount?.minus(2) ?: 0) { // minus(2) -> 2 is span count
outRect.bottom = spacing
}
}
}
private fun isInTheFirstRow(position: Int, totalSpanCount: Int): Boolean =
position < totalSpanCount
private fun isFirstInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
if (totalSpanCount != spanSize) {
position % totalSpanCount == 0
} else true
private fun isLastInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
isFirstInRow(position + 1, totalSpanCount, spanSize)
private fun getTotalSpanCount(parent: RecyclerView): Int =
(parent.layoutManager as? GridLayoutManager)?.spanCount ?: 1
private fun getItemSpanSize(parent: RecyclerView, position: Int): Int =
(parent.layoutManager as? GridLayoutManager)?.spanSizeLookup?.getSpanSize(position) ?: 1
}
注意事项: 这对于跨度计数 2 的情况是特殊的
将 RecyclerView 装饰成这样的外观的最好和最简单的方法是什么?
这里的主要挑战是仅在项目之间设置分隔线,而不是在项目和 left/right 屏幕边框之间设置分隔线。
有什么想法吗?
我不知道你为什么需要它,但是 UI 使用 RecyclerView 装饰器很容易实现。
<!--Integer Value that number of column in RecyclerView-->
<integer name="photo_list_preview_columns">3</integer>
<!-- inter spacing between RecyclerView's Item-->
<dimen name="photos_list_spacing">10dp</dimen>
您可以根据需要更改photo_list_preview_columns和photos_list_spacing。
mRecylerView.addItemDecoration(new ItemDecorationAlbumColumns(
getResources().getDimensionPixelSize(R.dimen.photos_list_spacing),
getResources().getInteger(R.integer.photo_list_preview_columns)));
和装饰器(需要一些改造)
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class ItemDecorationAlbumColumns extends RecyclerView.ItemDecoration {
private int mSizeGridSpacingPx;
private int mGridSize;
private boolean mNeedLeftSpacing = false;
public ItemDecorationAlbumColumns(int gridSpacingPx, int gridSize) {
mSizeGridSpacingPx = gridSpacingPx;
mGridSize = gridSize;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
int padding = parent.getWidth() / mGridSize - frameWidth;
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
if (itemPosition < mGridSize) {
outRect.top = 0;
} else {
outRect.top = mSizeGridSpacingPx;
}
if (itemPosition % mGridSize == 0) {
outRect.left = 0;
outRect.right = padding;
mNeedLeftSpacing = true;
} else if ((itemPosition + 1) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.right = 0;
outRect.left = padding;
} else if (mNeedLeftSpacing) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx - padding;
if ((itemPosition + 2) % mGridSize == 0) {
outRect.right = mSizeGridSpacingPx - padding;
} else {
outRect.right = mSizeGridSpacingPx / 2;
}
} else if ((itemPosition + 2) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx - padding;
} else {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx / 2;
}
outRect.bottom = 0;
}
}
您可能得到了答案,但我仍在发布可以帮助其他人的解决方案。这可以通过传递方向用于垂直、水平列表或网格视图。
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
public static final int GRID = 2;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST && orientation != GRID) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else if(mOrientation == HORIZONTAL_LIST){
drawHorizontal(c, parent);
} else {
drawVertical(c, parent);
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
if (parent.getChildCount() == 0) return;
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final View child = parent.getChildAt(0);
if (child.getHeight() == 0) return;
final RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
int bottom = top + mDivider.getIntrinsicHeight();
final int parentBottom = parent.getHeight() - parent.getPaddingBottom();
while (bottom < parentBottom) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
top += mDivider.getIntrinsicHeight() + params.topMargin + child.getHeight() + params.bottomMargin + mDivider.getIntrinsicHeight();
bottom = top + mDivider.getIntrinsicHeight();
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + params.rightMargin + mDivider.getIntrinsicHeight();
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else if(mOrientation == HORIZONTAL_LIST) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
}
}
@BindingAdapter({"bind:adapter"})
public static void bind(RecyclerView view, RecyclerView.Adapter<BaseViewHolder> adapter) {
view.setLayoutManager(new GridLayoutManager(view.getContext(), 3));
view.addItemDecoration(new SpacesItemDecorationGrid(view.getContext(), 4, 3));
view.setItemAnimator(new DefaultItemAnimator());
view.setAdapter(adapter);
}
public class SpacesItemDecorationGrid extends RecyclerView.ItemDecoration {
private int mSizeGridSpacingPx;
private int mGridSize;
private boolean mNeedLeftSpacing = false;
/**
* @param gridSpacingPx
* @param gridSize
*/
SpacesItemDecorationGrid(Context context, int gridSpacingPx, int gridSize) {
mSizeGridSpacingPx = (int) Util.convertDpToPixel(gridSpacingPx, context);
mGridSize = gridSize;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int frameWidth = (int) ((parent.getWidth() - (float) mSizeGridSpacingPx * (mGridSize - 1)) / mGridSize);
int padding = parent.getWidth() / mGridSize - frameWidth;
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
int itemCount = parent.getAdapter().getItemCount() - mGridSize;
/* if (itemPosition < mGridSize) {
outRect.top = mSizeGridSpacingPx;
} else {
outRect.top = mSizeGridSpacingPx;
}*/
outRect.top = mSizeGridSpacingPx;
if (itemPosition % mGridSize == 0) {
outRect.left = mSizeGridSpacingPx;
outRect.right = padding;
mNeedLeftSpacing = true;
} else if ((itemPosition + 1) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.right = mSizeGridSpacingPx;
outRect.left = padding;
} else if (mNeedLeftSpacing) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx - padding;
if ((itemPosition + 2) % mGridSize == 0) {
outRect.right = mSizeGridSpacingPx - padding;
} else {
outRect.right = mSizeGridSpacingPx / 2;
}
} else if ((itemPosition + 2) % mGridSize == 0) {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx - padding;
} else {
mNeedLeftSpacing = false;
outRect.left = mSizeGridSpacingPx / 2;
outRect.right = mSizeGridSpacingPx / 2;
}
if (itemPosition > itemCount) {
outRect.bottom = mSizeGridSpacingPx;
} else {
outRect.bottom = 0;
}
}
}
另一个对我有用的简单解决方案。希望有用。
class GridItemDecorator(val context: Context, private val spacingDp: Int, private val mGridSize: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val resources = context.resources
val spacingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingDp.toFloat(), resources.displayMetrics)
val bit = if (spacingPx > mGridSize) Math.round(spacingPx / mGridSize) else 1
val itemPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition
outRect.top = if (itemPosition < mGridSize) 0 else bit * mGridSize
outRect.bottom = 0
val rowPosition = itemPosition % mGridSize
outRect.left = rowPosition * bit
outRect.right = (mGridSize - rowPosition - 1) * bit
}
}
下面是一个更简单、对用户更友好的实现:
public class MediaSpaceDecoration extends RecyclerView.ItemDecoration {
private final int spacing;
private final List<Integer> allowedViewTypes = Arrays.asList(
R.layout.item_image,
R.layout.item_blur);
public MediaSpaceDecoration(int spacing) {
this.spacing = spacing;
}
@Override
public void getItemOffsets(Rect outRect,
View view,
RecyclerView parent,
RecyclerView.State state) {
final int position = parent.getChildAdapterPosition(view);
if (!isMedia(parent, position)) {
return;
}
final int totalSpanCount = getTotalSpanCount(parent);
int spanSize = getItemSpanSize(parent, position);
if (totalSpanCount == spanSize) {
return;
}
outRect.top = isInTheFirstRow(position, totalSpanCount) ? 0 : spacing;
outRect.left = isFirstInRow(position, totalSpanCount) ? 0 : spacing / 2;
outRect.right = isLastInRow(position, totalSpanCount) ? 0 : spacing / 2;
outRect.bottom = 0; // don't need
}
private boolean isInTheFirstRow(int position, int spanCount) {
return position < spanCount;
}
private boolean isFirstInRow(int position, int spanCount) {
return position % spanCount == 0;
}
private boolean isLastInRow(int position, int spanCount) {
return isFirstInRow(position + 1, spanCount);
}
private int getTotalSpanCount(RecyclerView parent) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager
? ((GridLayoutManager) layoutManager).getSpanCount()
: 1;
}
private int getItemSpanSize(RecyclerView parent, int position) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager
? ((GridLayoutManager) layoutManager).getSpanSizeLookup().getSpanSize(position)
: 1;
}
private boolean isMedia(RecyclerView parent, int viewPosition) {
final RecyclerView.Adapter adapter = parent.getAdapter();
final int viewType = adapter.getItemViewType(viewPosition);
return allowedViewTypes.contains(viewType);
}
}
我也在设置 outRect
之前进行检查,因为每个 viewType
我都有不同的 spanSize
,我需要为 [ 添加一个额外的中间 space =14=]。您可以轻松删除该验证,代码会更简单。对我来说看起来像这样:
这是我在 Kotlin 中的实现。我修改了其他答案的代码,以便也为完整的项目显示分隔符。
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class GridDividerItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val totalSpanCount = getTotalSpanCount(parent)
val spanSize = getItemSpanSize(parent, position)
outRect.top = if (isInTheFirstRow(position, totalSpanCount)) 0 else spacing
outRect.left = if (isFirstInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.right = if (isLastInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.bottom = 0
}
private fun isInTheFirstRow(position: Int, totalSpanCount: Int): Boolean =
position < totalSpanCount
private fun isFirstInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
if (totalSpanCount != spanSize) {
position % totalSpanCount == 0
} else true
private fun isLastInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
isFirstInRow(position + 1, totalSpanCount, spanSize)
private fun getTotalSpanCount(parent: RecyclerView): Int =
(parent.layoutManager as? GridLayoutManager)?.spanCount ?: 1
private fun getItemSpanSize(parent: RecyclerView, position: Int): Int =
(parent.layoutManager as? GridLayoutManager)?.spanSizeLookup?.getSpanSize(position) ?: 1
}
结果:
下面是我的自定义 class,它允许 Kotlin 中的网格单元格之间的间距相等:
class GridItemOffsetDecoration(private val spanCount: Int, private var mItemOffset: Int) : ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
if (position < spanCount) {
if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset, mItemOffset / 2, mItemOffset / 2)
} else { // right grid
outRect.set(mItemOffset / 2, mItemOffset, 0, mItemOffset / 2)
}
} else if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset / 2)
} else if (position % 2 == 1) { // right grid
outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset / 2)
} else {
if (position % 2 == 0) { // left grid
outRect.set(0, mItemOffset / 2, mItemOffset, mItemOffset)
} else { // right grid
outRect.set(mItemOffset / 2, mItemOffset / 2, 0, mItemOffset)
}
}
}
}
并将其添加为 RecyclerView 中的项目装饰器,添加以下行:
/*spanCount is the number of grids, for instance, (2 = 2*2 grid, 3 = 3*3)*/
binding.rvActiveChallenges.addItemDecoration(GridItemOffsetDecoration(2, resources.getDimensionPixelSize(R.dimen._10dp)))
这是我的版本。基于
注意:计算每个图像间距的逻辑比仅将间距除以二(每个图像减半)更复杂,大多数答案都没有考虑到这一点..
/**
* Class to add INTERNAL SPACING to a grid of items. Only works for a grid with 3 columns or more.
*/
class PhotoSpaceDecoration extends RecyclerView.ItemDecoration {
private final int spacingWidthPx;
/**
* Initialise with the with of the spacer in dp
*
* @param spacingWidthDp this will be divided between elements and applied as a space on each side
* NB: for proper alignment this must be divisible by 2 and by the number of columns
*/
public PhotoSpaceDecoration(Context context, int spacingWidthDp) {
// Convert DP to pixels
this.spacingWidthPx = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, spacingWidthDp,
context.getResources().getDisplayMetrics());
}
/**
* @param index a 0 indexed value of the current item
* @param numberOfColumns
* @return a 0 indexed Point with the x & y location of the item in the grid
*/
private Point getItemXY(int index, int numberOfColumns) {
int x = index % numberOfColumns;
int y = index / numberOfColumns; // NB: integer division
return new Point(x, y);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final int position = parent.getChildAdapterPosition(view);
final int columns = getTotalSpanCount(parent);
final int rows = (int) Math.ceil(parent.getChildCount() / (double) columns); // NB: NOT integer division
int spanSize = getItemSpanSize(parent, position);
if (columns == spanSize) {
return;
}
Point point = getItemXY(position, columns);
int firstMargin = spacingWidthPx * (columns - 1) / columns;
int secondMargin = spacingWidthPx - firstMargin;
int middleMargin = spacingWidthPx / 2;
if (point.x == 0) { // first column
outRect.left = 0;
outRect.right = firstMargin;
} else if (point.x == 1) { // second column
outRect.left = secondMargin;
outRect.right = rows > 3 ? middleMargin : secondMargin;
} else if (point.x - columns == -2) { // penultimate column
outRect.left = rows > 3 ? middleMargin : secondMargin;
outRect.right = secondMargin;
} else if (point.x - columns == -1) { // last column
outRect.left = firstMargin;
outRect.right = 0;
} else { // middle columns
outRect.left = middleMargin;
outRect.right = middleMargin;
}
if (point.y == 0) { // first row
outRect.top = 0;
outRect.bottom = firstMargin;
} else if (point.y == 1) { // second row
outRect.top = secondMargin;
outRect.bottom = rows > 3 ? middleMargin : secondMargin;
} else if (point.y - rows == -2) { // penultimate row
outRect.top = rows > 3 ? middleMargin : secondMargin;
outRect.bottom = secondMargin;
} else if (point.y - rows == -1) { // last row
outRect.top = firstMargin;
outRect.bottom = 0;
} else { // middle rows
outRect.top = middleMargin;
outRect.bottom = middleMargin;
}
}
private int getTotalSpanCount(RecyclerView parent) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanCount() : 1;
}
private int getItemSpanSize(RecyclerView parent, int position) {
final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
return layoutManager instanceof GridLayoutManager ? ((GridLayoutManager) layoutManager).getSpanSizeLookup()
.getSpanSize(
position) : 1;
}
}
这适用于 Activity.onCreate()
的回收站视图,如下所示
photosRecyclerView.addItemDecoration(new PhotoSpaceDecoration(this, 6));
示例:
如果你header使用这个。
要隐藏 header 的分隔符设置 skipHeaderDivider=false
,否则设置 true
。
class GridDividerItemDecoration : ItemDecoration() {
var skipHeaderDivider = true
private var divider: Drawable? = null
private val bounds = Rect()
private var spacing = 0
fun setDrawable(drawable: Drawable) {
divider = drawable
divider?.intrinsicHeight?.let { spacing = it }
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
canvas.save()
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
parent.layoutManager?.getDecoratedBoundsWithMargins(child, bounds)
val right: Int = bounds.right + child.translationX.roundToInt()
val left: Int = bounds.left - child.translationX.roundToInt()
val bottom: Int = bounds.bottom + child.translationY.roundToInt()
val top: Int = bounds.top - child.translationY.roundToInt()
divider?.setBounds(left, top, right, bottom)
divider?.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val gridLayoutManager = parent.layoutManager as? GridLayoutManager ?: return
val position = gridLayoutManager.getPosition(view)
if (position < 0) return
val spanCount = gridLayoutManager.spanCount
val positionalSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position)
if (skipHeaderDivider && positionalSpanSize == spanCount) return
val itemCount = gridLayoutManager.itemCount
val onBottom = position >= itemCount - spanCount
var nextHeader = false
run loop@{
for (i in 1..spanCount) {
val nextSpanSize = gridLayoutManager.spanSizeLookup.getSpanSize(position + i)
if (nextSpanSize == spanCount) {
nextHeader = true
return@loop
}
}
}
outRect.top = spacing
outRect.left = 0
outRect.right = spacing
outRect.bottom = if (nextHeader || onBottom) spacing else 0
}
}
你可以试试我的解决方案。如果您想要列表视图或网格视图,这很灵活。
class GridItemOffsetDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val totalSpanCount = getTotalSpanCount(parent)
val spanSize = getItemSpanSize(parent, position)
// if list view span == 1
if (totalSpanCount == 1) {
if (position == 0) {
outRect.top = 10.dp
} else {
outRect.top = spacing
}
outRect.left = spacing
outRect.right = spacing
if (position == parent.adapter?.itemCount?.minus(1)) {
outRect.bottom = spacing
}
} else { // if grid view span >= 2
outRect.top = if (isInTheFirstRow(position, totalSpanCount)) 0 else spacing
outRect.left = if (isFirstInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.right = if (isLastInRow(position, totalSpanCount, spanSize)) 0 else spacing / 2
outRect.bottom = 0
when {
position % 2 == 0 -> {
outRect.left = spacing
}
else -> {
outRect.right = spacing
}
}
outRect.top = 10.dp
if (position >= parent.adapter?.itemCount?.minus(2) ?: 0) { // minus(2) -> 2 is span count
outRect.bottom = spacing
}
}
}
private fun isInTheFirstRow(position: Int, totalSpanCount: Int): Boolean =
position < totalSpanCount
private fun isFirstInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
if (totalSpanCount != spanSize) {
position % totalSpanCount == 0
} else true
private fun isLastInRow(position: Int, totalSpanCount: Int, spanSize: Int): Boolean =
isFirstInRow(position + 1, totalSpanCount, spanSize)
private fun getTotalSpanCount(parent: RecyclerView): Int =
(parent.layoutManager as? GridLayoutManager)?.spanCount ?: 1
private fun getItemSpanSize(parent: RecyclerView, position: Int): Int =
(parent.layoutManager as? GridLayoutManager)?.spanSizeLookup?.getSpanSize(position) ?: 1
}
注意事项: 这对于跨度计数 2 的情况是特殊的