outofmemory
outofmemory

Reputation: 231

How to avoid double space between items when using RecyclerView with StaggeredGridLayoutManager?

I'm using RecyclerView with StaggeredGridLayoutManager to make a two-column list. But how to set a right margin between left column and right column. I have used this code to make right margin from top, but how to solve double space between to columns.

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 or second item to avoid double space between items
        // Add top margin only for the first or second item to avoid double space between items
        if((parent.getChildCount() > 0 && parent.getChildPosition(view) == 0)
            || (parent.getChildCount() > 1 && parent.getChildPosition(view) == 1))
        outRect.top = space;
}

And in Activity:

recyclerView.addItemDecoration(new SpacesItemDecoration(20));

I've tried to use view.getX(), it always return 0.

Can anyone help me? Many thanks!

Upvotes: 20

Views: 12152

Answers (9)

Tatjana Djurdjevic
Tatjana Djurdjevic

Reputation: 151

The following code handles StaggeredGridLayout without problems(at least from my experience). You only need to give spacing to SpaceItemDecorator.

class SpaceItemDecoration(private val spacing: Int) :
    RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {

        val lm = parent.layoutManager as StaggeredGridLayoutManager
        val lp = view.layoutParams as StaggeredGridLayoutManager.LayoutParams

        val spanCount = lm.spanCount
        val spanIndex = lp.spanIndex
        val positionLayout = lp.viewLayoutPosition
        val itemCount = lm.itemCount
        val position = parent.getChildAdapterPosition(view)

        outRect.right = spacing / 2
        outRect.left = spacing / 2
        outRect.top = spacing / 2
        outRect.bottom = spacing / 2

        if (spanIndex == 0) outRect.left = spacing

        if (position < spanCount) outRect.top = spacing

        if (spanIndex == (spanCount - 1)) outRect.right = spacing

        if (positionLayout > (itemCount - spanCount)) outRect.bottom = spacing
    }
}

You can get spacing in pixels like this:

 val spacingInPixels = resources.getDimensionPixelSize(R.dimen.grid_layout_margin)

And then you just need to assign item decoration to your recycler view:

recycler_view.addItemDecoration(
                  SpaceItemDecoration(
                        spacingInPixels
                    )
                )

Upvotes: 0

Gabriel Perez
Gabriel Perez

Reputation: 403

I solved this in 3 steps by the following way:

  1. Add marginStart and marginEnd to the parent (RecyclerView) with the same value
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginStart="@dimen/margin_small"
    android:layout_marginEnd="@dimen/margin_small"
    app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
    app:spanCount="2" />
  1. Create a custom ItemDecorator class and extend of RecyclerView.ItemDecoratior. Get marigns parent and then modifying the internal margins in a half of each child according to the case
class SpaceItemDecorator : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        super.getItemOffsets(outRect, view, parent, state)

        val layoutParams = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
        val spanIndex = layoutParams.spanIndex

        if (spanIndex == 0) {
            val marginParentEnd = parent.marginEnd
            layoutParams.marginEnd = marginParentEnd / 2
        } else {
            val marginParentStart = parent.marginStart
            layoutParams.marginStart = marginParentStart / 2
        }

        view.layoutParams = layoutParams
    }
}
  1. Add the custom ItemDecorator to the recyclerView
recyclerview.addItemDecoration(SpaceItemDecorator())

That is all, without margins/paddings hardcode and in Koltin xD

note: It is important not to establish any horizontal margin in the child's view layout

Upvotes: 0

Gabriel Cs&#233;falvay
Gabriel Cs&#233;falvay

Reputation: 539

It is possible to set padding to the two sides of the RecyclerView:

myRecyclerView.setPadding(10,0,10,0);

In this way you can equalize the space on the sides and between the columns.

Upvotes: 1

Donny
Donny

Reputation: 61

public class EqualGapItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;

    public EqualGapItemDecoration(int spanCount, int spacing) {
        this.spanCount = spanCount;
        this.spacing = spacing;
    }

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

        StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();

        if (layoutParams.isFullSpan()) {
            outRect.set(0, 0, 0, 0);
        } else {
            int spanIndex = layoutParams.getSpanIndex();
            int layoutPosition = layoutParams.getViewLayoutPosition();
            int itemCount = parent.getAdapter().getItemCount();

            boolean leftEdge = spanIndex == 0;
            boolean rightEdge = spanIndex == (spanCount - 1);

            boolean topEdge = spanIndex < spanCount;
            boolean bottomEdge = layoutPosition >= (itemCount - spanCount);

            int halfSpacing = spacing / 2;

            outRect.set(
                    leftEdge ? spacing : halfSpacing,
                    topEdge ? spacing : halfSpacing,
                    rightEdge ? spacing : halfSpacing,
                    bottomEdge ? spacing : 0
            );
        }
    }
}

Upvotes: 6

Hardy
Hardy

Reputation: 2606

I would slightly change and modify both of the answer for relative question

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
    private final int mSpace;

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

    @Override
    public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int position = parent.getChildAdapterPosition(view);
        int spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
        if (spanIndex == 0) {
            outRect.left = 30;
            outRect.right = 15;
        } else {//if you just have 2 span . Or you can use (staggeredGridLayoutManager.getSpanCount()-1) as last span
            outRect.left = 15;
            outRect.right = 30;
        }
        outRect.bottom = 30;
        // Add top margin only for the first item to avoid double space between items
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.top = 30;
            outRect.right = 30;
        }
    }
}

Upvotes: 0

emotionfxxk
emotionfxxk

Reputation: 329

Set different left/right space in ItemDecoration according to spanindex only works well for item view has fixed size. If the item view contain image view set wrap_content in height, the item view may change span position, but span index not updated as you wish, the margin will be a mess.

My way is, using the symmetrical left/right space for view item.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // init
    recyclerView = (RecyclerView) rv.findViewById(R.id.img_waterfall);
    layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(new MyAdapter(getContext()));
    recyclerView.addItemDecoration(new SpacesItemDecoration(
        getResources().getDimensionPixelSize(R.dimen.space)));
}


private static class SpacesItemDecoration extends RecyclerView.ItemDecoration {
    private final int space;
    public SpacesItemDecoration(int space) {
        this.space = space;
    }
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                               RecyclerView.State state) {
        outRect.bottom = 2 * space;
        int pos = parent.getChildAdapterPosition(view);
        outRect.left = space;
        outRect.right = space;
        if (pos < 2)
            outRect.top = 2 * space;
    }
}

Then set the same padding for RecylerView(left/right) android:paddingLeft="@dimen/space" android:paddingRight="@dimen/space"

    <android.support.v7.widget.RecyclerView
        android:id="@+id/img_waterfall"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/space"
        android:paddingRight="@dimen/space"/>

Upvotes: 0

Antikvo
Antikvo

Reputation: 81

In your case you can setting the both margin to the RecyclerView .But in my case there is a image match the screen width in the RecyclerView ,and I use the ItemDecoration to solve the problem.

  class ViewItemDecoration extends RecyclerView.ItemDecoration {

    public ViewItemDecoration() {
    }

    @Override
    public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int position = parent.getChildAdapterPosition(view);
        int spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
        int type = adapter.getItemViewType(position);
        switch(type){
          case YOUR_ITEMS:
                if (spanIndex == 0) {
                    outRect.left = 10;
                    outRect.right = 5;
                } else {//if you just have 2 span . Or you can use (staggeredGridLayoutManager.getSpanCount()-1) as last span
                    outRect.left = 5;
                    outRect.right = 10;
                }
        }
    }

}

Upvotes: 8

Mark Hetherington
Mark Hetherington

Reputation: 1701

The following code will handle StaggeredGridLayoutManager, GridLayoutManager, and LinearLayoutManager.

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int halfSpace;

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

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

        if (parent.getPaddingLeft() != halfSpace) {
            parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
            parent.setClipToPadding(false);
        }

        outRect.top = halfSpace;
        outRect.bottom = halfSpace;
        outRect.left = halfSpace;
        outRect.right = halfSpace;
    }
}

Upvotes: 10

outofmemory
outofmemory

Reputation: 231

I solved it myself by setting item margin and RecyclerView padding. Both margin and padding are half of expected space, so the problem disappeared.

Upvotes: 3

Related Questions