AndroidRecyclerviewGridLayoutManager列間距 - Android Recyclerview GridLayoutManager column spacing

yangxi_001發表於2017-08-29
解決方案:
RecyclerViews支援ItemDecoration的概念:特殊補償和繪畫在每個元素。見這個答案,您可以使用然後通過新增
原文:

RecyclerViews support the concept of ItemDecoration: special offsets and drawing around each element. As seen in this answer, you can use

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

Then add it via

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));
網友:使用“outRect。頂級=空間outRect和刪除。底,如果你不想惹“如果第一的位置”。;-)

(原文:Use 'outRect.top = space' and remove 'outRect.bottom' if you don't want to mess with the 'if for the first position'. ;-])

網友:@zatziky——是的,如果你已經在使用頂部和底部填充作為你的一部分(使用),然後你可以稍微重組的事情。然而,如果你不你只是移動如果檢查最後一次(就像你仍然希望底部填充最後一項)。

(原文:@zatziky - yep, if you already use top and bottom padding as part of your RecyclerView (and useclipToPadding="false"), then you can restructure things slightly. If you don't however, you'd just be moving the if check to be the last time (as you'd still want the bottom padding on the last item).)

網友:我試著這個,除非我新增了Android:clipToPadding=“false”和android:填充=“@dimen/間距”RecyclerView外面的間距不符合項之間的間距。我已經試了所有下面的答案,認為@yqritc是最乾淨,最簡單的解決方案。

(原文:I tried this and unless I added android:clipToPadding="false" and android:padding="@dimen/spacing" to the RecyclerView the outside spacing did not match the spacing between items. I have tried all the answers below and think the solution by @yqritc was the cleanest and simplest.)

網友:@ianhanniballake,而這是在使用單孔佈局管理器,這對多跨佈局管理器失敗。

(原文:@ianhanniballake, while this works when using a single span layout manager, it fails for multi-span layout manager.)

網友:值得提及,你必須修改outRect的所有欄位。否則你可以抵消之前的觀點。

(原文:It worth to mention, that you have to modify all fields of outRect. otherwise you can get the offset of the previous view.)

解決方案:
程式碼執行良好,每一列有相同的寬度:使用它:1)沒有邊緣2)與邊緣
原文:

Following code works well, and each column has same width:

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;
    private boolean includeEdge;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
        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) {
            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 {
            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
            }
        }
    }
}

To use it:

1) no edge

enter image description here

int spanCount = 3;
int spacing = 50;
boolean includeEdge = false;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));

2) with edge

enter image description here

int spanCount = 3;
int spacing = 50;
boolean includeEdge = true;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
網友:謝謝,它太棒了!

(原文:thanks, It works great!)

網友:工作除非你有專案,有各種各樣的跨越,像標題。

(原文:Works unless you have items that have various spans, like headers.)

網友:工作就像一個魅力!

(原文:Worked like a charm!)

網友:似乎只有在垂直模式工作

(原文:Seems to only work for vertical mode)

解決方案:
下面是一步一步簡單的解決方案,如果你想要的物品和周圍的相等的間距等於專案大小。ItemOffsetDecoration實現在你的原始碼,新增ItemOffsetDecorationrecyclerview。項抵消值應該是您想要新增一半大小的實際價值之間的空間專案。同時,設定專案抵消值作為其recyclerview填充,並指定android:clipToPadding=false。完成了。
原文:

The following is the step-by-step simple solution if you want the equal spacing around items and equal item sizes.

ItemOffsetDecoration

public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {

    private int mItemOffset;

    public ItemOffsetDecoration(int itemOffset) {
        mItemOffset = itemOffset;
    }

    public ItemOffsetDecoration(@NonNull Context context, @DimenRes int itemOffsetId) {
        this(context.getResources().getDimensionPixelSize(itemOffsetId));
    }

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

Implementation

In your source code, add ItemOffsetDecoration to your recyclerview.
Item offset value should be half size of the actual value you want to add as space between items.

mRecyclerView.setLayoutManager(new GridLayoutManager(context, NUM_COLUMNS);
ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(context, R.dimen.item_offset);
mRecyclerView.addItemDecoration(itemDecoration);

Also, set item offset value as padding for its recyclerview, and specify android:clipToPadding=false.

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview_grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:padding="@dimen/item_offset"/>

DONE.

網友:簡單而優雅

(原文:Simple and elegant)

網友:最佳答案——偉大的工作!

(原文:Best answer here - great job!)

網友:最佳答案-m8:感謝)

(原文:best answer- thanks m8 :))

網友:完美!就像一個魅力。

(原文:perfect! Works like a charm.)

網友:好先生!!

(原文:Well done sir!!)

解決方案:
試試這個。它會照顧相等的間距。作品都使用列表、網格,進行。編輯更新的程式碼應該處理的大部分角落案件與跨越,取向,等等。請注意,如果使用setSpanSizeLookupGridLayoutManager(),設定setSpanIndexCacheEnabled()建議由於效能的原因。與進行注意,似乎,似乎有一個錯誤的索引孩子變得古怪,很難跟蹤,所以下面的程式碼與StaggeredGridLayoutManager可能不會工作得很好。希望它可以幫助。
原文:

Try this. It'll take care of equal spacing all around. Works both with List, Grid, and StaggeredGrid.

Edited

The updated code should handle most of the corner cases with spans, orientation, etc. Note that if using setSpanSizeLookup() with GridLayoutManager, setting setSpanIndexCacheEnabled() is recommended for performance reasons.

Note, it seems that with StaggeredGrid, there's seems to be a bug where the index of the children gets wacky and hard to track so the code below might not work very well with 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);
  }
}

Hope it helps.

網友:非常感謝對這個答案

(原文:thnx for this answer)

網友:這應該是驗證答案。謝謝你!

(原文:This should be the verified answer. Thank you.)

網友:我有雙跨後的第一道物品。這是因為parent.getChildCount第一項()返回1,2第二等等。所以,我建議新增空間專案的前邊緣:outRect。頂級=childIndex<spancount嗎?spacinginpixels:0,和每個條目的新增底空間:outrect。底=spacinginpixels;

(原文:I've got double span just after first line of items. It happens because parent.getChildCount() returns 1 for first item, 2 for second and so on. So, I suggest add space to items of the top edge like: outRect.top = childIndex < spanCount ? spacingInPixels : 0; And add bottom space for each item: outRect.bottom = spacingInPixels;)

網友:在滾動的時候RecyclerView,間距變了。

(原文:At the time of scrolling RecyclerView, spacing changed.)

網友:我認為parent.getChildCount()應該改為“parent.getLayoutManager().getItemCount()”。同時,isBottomEdge功能需要改變“迴歸childIndex>=childCount-spanCount+spanIndex”。改變這些之後,我得到了相等的間距。但是請注意,這個解決方案不給我等於專案大小如果跨數大於2因為偏移值取決於位置是不同的。

(原文:I think parent.getChildCount() should be changed to "parent.getLayoutManager().getItemCount()". Also, isBottomEdge function need to be changed to "return childIndex >= childCount - spanCount + spanIndex". After changing these, I got equal spacing. But please note that this solution does not give me equal item sizes if span count is greater than 2 since offset value is different depending on position.)

解決方案:
下面的程式碼將處理StaggeredGridLayoutManager、GridLayoutManagerLinearLayoutManager。然後使用它
原文:

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

Then use it

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));
網友:這是最簡單的一個。一個重要的事情是你也要新增填充到父在xml。在我的例子中,它的工作方式。謝謝。

(原文:This is the most simple one. One important thing is you also got to add the padding to the parent in the xml. In my case, it work that way. Thanks.)

網友:實際上增加了填充到父(回收商的觀點)。

(原文:The SpaceItemDecoration actually adds the padding to the parent (the recycler view).)

網友:只填充出現(右側)當我沒有設定填充到父在xml中

(原文:only halfSpace padding appeared(to the right side) when I had not set the padding to the parent in xml)

網友:只是缺失的右邊?也許你有半空間設定為leftPadding左邊已經在xml,這段程式碼只剩下檢查填充設定RecyclerView與否。

(原文:It was only missing on the right side? It may be that you have half space set as the leftPadding on the left side already in the xml and this code only checks if the left padding is set on the RecyclerView or not.)

網友:這是最簡潔的解決方案!

(原文:this is the most concise solution!)

解決方案:
回答上面有澄清方法設定邊緣處理GridLayoutManager和LinearLayoutManager。但是StaggeredGridLayoutManagerPirdadSakhizada的回答說,“與StaggeredGridLayoutManager可能不會工作得很好。“這應該對indexOfSpan問題。你可以通過這種方式:
原文:

Answers above have clarified ways to set margin handling GridLayoutManager and LinearLayoutManager.

But for StaggeredGridLayoutManager, Pirdad Sakhizada's answer say it "might not work very well with StaggeredGridLayoutManager." It should be the problem about indexOfSpan.

You can get it by this way:

private static class MyItemDecoration extends RecyclerView.ItemDecoration {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int index = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
    }
}

相關文章