Decorating RecyclerView (with GridLayoutManager) to display divider between items
Asked Answered
W

10

49

What's the best and easiest way to decorate RecyclerView to have such look & feel?

enter image description here

The main challenge here is having dividers only between items, but not between items and left/right borders of screen.

Any ideas?

Wroughtup answered 19/3, 2015 at 14:7 Comment(5)
see developer.android.com/reference/android/support/v7/widget/…, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State)Fucoid
@Fucoid I see that it's necessary to create/reuse some kind of item decoration. The question is about concrete solution.Wroughtup
@Fucoid what's correct way to determine wether item has right / left neighbours or not within getItemOffsets method?Wroughtup
RecyclerView.getChildPosition(View child) gives you the item position, and the position determines where the item is lay out on the RecyclerViewFucoid
@Fucoid how to understand is it around border or not (in general case) ?Wroughtup
L
118

I don't know why do you need that, but this UI is quite easy to implement with RecyclerView decorator.

<!--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>

You can change photo_list_preview_columns and photos_list_spacing according to your needs.

mRecylerView.addItemDecoration(new ItemDecorationAlbumColumns(
    getResources().getDimensionPixelSize(R.dimen.photos_list_spacing), 
    getResources().getInteger(R.integer.photo_list_preview_columns)));

and decorator (needs some refatoring)

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;
    }
}

enter image description here enter image description here

Lanalanae answered 20/3, 2015 at 13:58 Comment(8)
@Wroughtup the same UI but with Load more items howthis can be achieved ?Messenger
This is a good start but this draw lines underneath the bottom row when items are animating (i.e., notifyItemChanged called)Demivolt
If I could I would just come and hug you bro. This is the thing I was looking for. ThanksAntigua
What is mean of mNeedLeftSpacing state variable? Does it relay on call ordering?Citrin
How to add color into divider?Heliostat
Thanks! You can remove frameWidth and set int padding = mSizeGridSpacingPx - mSizeGridSpacingPx / mGridSize;Horseback
@WaqarVicky, see Yeldar.N solution with drawable and onDraw.Horseback
if you're using ConcatAdapter then getViewAdapterPosition() will return individual adapters item position which is wrong. for this we need to use getAbsoluteAdapterPosition() which will return the actual position respect to RecyclerView.Clementclementas
C
12

Here's a simpler and more user-friendly implementation:

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);
    }
}

I also check before setting the outRect because I have various spanSizes for each viewType and I need to add an extra middle-space only for allowedViewTypes. You can easily remove that verification and the code would be even simpler. It looks like this for me: GridLayout with a different spanSize for each viewType

Citole answered 26/12, 2017 at 13:43 Comment(3)
Thanks. I took out the allowedViewTypes and is Media and also set outRect.top to 0 because I wanted only a space in the middle between columns, not between rows.Selfsown
This doesn't work for a grid > 2 images wide as then you can't just divide the spacing by 2 and it depends on the position of the image. Further details in my answer below: https://mcmap.net/q/349591/-decorating-recyclerview-with-gridlayoutmanager-to-display-divider-between-itemsTransubstantiation
What are item_image, item_blur?Horseback
N
6

You may got your answer but i'm still posting my solution that can help others. This can be used for vertical, horizontal lists or grid views by passing the orientation.

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());
        }
    }
}
Napkin answered 15/9, 2015 at 11:38 Comment(1)
Doesn't work well in case the rows of the grid are of different sizesIde
P
5

Here's my implementation in Kotlin. I modified code from other answers so that divider is shown also for full spanned items.

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
}

Result:

grid_divider_icon_decoration

Polygenesis answered 18/10, 2019 at 10:43 Comment(7)
What if there is a full spanned item at 0 position ? The defined methods isInTheFirstRow, isFirstInRow, isLastInRow doesn't seem generic. Is it intended only for the specific use case of yours as shown in the image or a generic one ?Magma
It's supposed to be generic, but you're right, I think full spanned item at 0 position is not covered. I didn't try though.Polygenesis
Also, how do you set custom color these decoration lines ? Not for all dividers in the app (android:listDivider), just for this recyclerview ? Any idea ?Magma
You can add <item name="android:listDivider">@drawable/divider_grid</item> to style of your RecyclerView.Polygenesis
This will apply to all dividers in the app, we need to apply just for the specific decorator only.Magma
How you add that blue color in divider?Heliostat
@WaqarVicky it's a background color of RecyclerViewPolygenesis
A
3

One more simple solution that worked for me. Hope it can be useful.

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

    }
}
Anaphylaxis answered 25/5, 2017 at 14:15 Comment(0)
T
2

Here's my version. Based on Nicolay's answer but improved to work with a grid of three or more images & uses dp units for spacing. (His version doesn't give equal sized images/spacing with more than 2 images.)

NB: the logic to calculate spacing on each image is more complex than just dividing the spacing by two (half for each image) which most answers don't account for..

/**
 * 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;
    }
}

This is applied to the recycler view from the Activity.onCreate() as below

photosRecyclerView.addItemDecoration(new PhotoSpaceDecoration(this, 6));

Example: enter image description here

Transubstantiation answered 19/8, 2020 at 14:6 Comment(0)
C
1
  @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;
        }

    }

}
Chanson answered 13/5, 2017 at 16:2 Comment(1)
Please consider adding some explanation of what the code does. Code-only answers may technically answer the question but are less likely to be useful to other users with similar problems.Revareval
C
1

Below is my custom class that allows equal spacing between grid cells in 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)
        }
    }
}

}

And to add this as a Item Decorator in RecyclerView, add below line:

/*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)))
Celebes answered 10/8, 2020 at 13:13 Comment(0)
D
1

If you have header use this.

To hide the divider of header set skipHeaderDivider=false, otherwise set 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
    }
}
Duer answered 4/11, 2020 at 15:24 Comment(0)
H
0

You can try my solution. this is flexible if you want list view or grid view.

    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
}

NOTES : this is special for span count 2 case

Headliner answered 15/9, 2021 at 14:34 Comment(2)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Admirable
Code-only answers are discouraged, so add some explanation to your answer. While that code may be fine, it is much more helpful if you also explain what it does, how it answers the question, and how it differs in approach from all the existing answers.Olivares

© 2022 - 2024 — McMap. All rights reserved.