Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView ItemDecoration Spacing and Span

I have a GridSpacingItemDecoration class that manages spacing and spans.
here is the code:

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration
{
    private int spanCount;
    private int spacing;
    private boolean includeEdge;
    private boolean rtl;

    public GridSpacingItemDecoration(boolean rtl, int spanCount, int spacing, boolean includeEdge)
    {
        this.rtl = rtl;
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
    {
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column

        if (includeEdge)
        {
            if (rtl)
            {
                outRect.right = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.left = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
            }else {
                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
            }

            if (position < spanCount)
            { // top edge
                outRect.top = spacing;
            }
            outRect.bottom = spacing; // item bottom
        } else
        {
            if (rtl){
                outRect.right = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.left = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            }else {
                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            }

            if (position >= spanCount)
            {
                outRect.top = spacing; // item top
            }
        }
    }
}

It works well when i want to have one or more columns. (shown in pictures below - all spacing and span works)
enter image description here
enter image description here
The problem is that i want to use a RecyclerView that has different ViewTypes with different spanCount. here is how i tried to do it:
defined in class:

public static ArrayList<Integer> type = new ArrayList<>();

private int getTypeForPosition(int position)
{
    return type.get(position);
}

private final int HEADER = 0;
private final int CHILD = 1;
private int dpToPx(int dp)
{
    Resources r = getResources();
    return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}

defined in method:

type.add(HEADER);
type.add(CHILD);
type.add(CHILD);
type.add(HEADER);
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
    switch(getTypeForPosition(position)) {
        case HEADER:
            return 2;
        default:
            return 1;
        }
    }
});
recyclerView.setLayoutManager(glm);
recyclerView.addItemDecoration(new GridSpacingItemDecoration(true, 1, dpToPx(8), true));
ClassAdapter classAdapter = new ClassAdapter(getContext(), classes);
recyclerView.setAdapter(classAdapter);

here is the result:
enter image description here

The problem is: the space between two columns in the same row (shown in picture). it seems to be 16, twice what i have choosed.
Question: How to customize GridSpacingItemDecoration class to have same space between all items?

like image 406
Masoud Mohammadi Avatar asked Aug 13 '17 13:08

Masoud Mohammadi


1 Answers

The way to do this is to read the layout parameters of the view.

GridLayoutManager.LayoutParams params =
        (GridLayoutManager.LayoutParams) view.getLayoutParameters()

Those layout parameters have the following properties:

// Returns the current span index of this View.
int getSpanIndex()

// Returns the number of spans occupied by this View.
int getSpanSize()

This way you can check in which column a view is and how many columns it spans.

  • If it is in column 0 you apply the full offset on the start side, else only half

  • If spanIndex + spanSize equals spanCount (it occupies the last column) you apply the full offset on the end, else only half.

For better reusability you should also consider using

((GridLayoutManager) parent.getLayoutManager()).getSpanCount()

to get the count of total spans instead of setting it in the constructor. This way you can dynamically change / update the span count and it will still work.

Please don't forget to check instanceof before casting and throwing proper exceptions or something ;)


Following these instructions to the letter, we end up with the following decoration:

class GridSpanDecoration extends RecyclerView.ItemDecoration {
  private final int padding;

  public GridSpanDecoration(int padding) {
    this.padding = padding;
  }

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

    GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
    int spanCount = gridLayoutManager.getSpanCount();

    GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) view.getLayoutParams();

    int spanIndex = params.getSpanIndex();
    int spanSize = params.getSpanSize();

    // If it is in column 0 you apply the full offset on the start side, else only half
    if (spanIndex == 0) {
      outRect.left = padding;
    } else {
      outRect.left = padding / 2;
    }

    // If spanIndex + spanSize equals spanCount (it occupies the last column) you apply the full offset on the end, else only half.
    if (spanIndex + spanSize == spanCount) {
      outRect.right = padding;
    } else {
      outRect.right = padding / 2;
    }

    // just add some vertical padding as well
    outRect.top = padding / 2;
    outRect.bottom = padding / 2;

    if(isLayoutRTL(parent)) {
      int tmp = outRect.left;
      outRect.left = outRect.right;
      outRect.right = tmp;
    }
  }

  @SuppressLint({"NewApi", "WrongConstant"})
  private static boolean isLayoutRTL(RecyclerView parent) {
    return parent.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
  }
}

Which allows for an arbitrary amount of columns and will align them correctly.

Grid Span Decoration

like image 54
David Medenjak Avatar answered Nov 11 '22 15:11

David Medenjak