首页 文章

Android Recyclerview GridLayoutManager列间距

提问于
浏览
175

如何使用GridLayoutManager使用RecyclerView设置列间距?在我的布局中设置边距/填充无效 .

23 回答

  • 277

    RecyclerViews支持ItemDecoration的概念:特殊偏移和绘制每个元素 . 如this answer中所示,您可以使用

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

    然后通过添加它

    mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
    int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
    mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));
    
  • 4

    复制的@edwardaa提供了代码,我完全支持RTL:

    public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
        private int spanCount;
        private int spacing;
        private boolean includeEdge;
        private int headerNum;
        private boolean isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;
    
        public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) {
            this.spanCount = spanCount;
            this.spacing = spacing;
            this.includeEdge = includeEdge;
            this.headerNum = headerNum;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view) - headerNum; // item position
            if (position >= 0) {
                int column = position % spanCount; // item column
                if(isRtl) {
                    column = spanCount - 1 - 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
                    }
                }
            } else {
                outRect.left = 0;
                outRect.right = 0;
                outRect.top = 0;
                outRect.bottom = 0;
            }
        }
    }
    
  • 13

    以下代码运行良好,每列具有相同的宽度:

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

    用法

    1.没有优势

    enter image description here

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

    2.有边缘

    enter image description here

    int spanCount = 3; // 3 columns
    int spacing = 50; // 50px
    boolean includeEdge = true;
    recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
    
  • 0

    如果您希望项目周围的间距相等且项目大小相等,则以下是逐步简单的解决方案 .

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

    实施

    在源代码中,将 ItemOffsetDecoration 添加到 RecyclerView. 项目偏移值应该是要添加为项目之间空间的实际值的一半 .

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

    另外,将项目偏移值设置为 RecyclerView 的填充,并指定 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"/>
    
  • 0

    试试这个 . 它会照顾到周围相等的间距 . 适用于List,Grid和StaggeredGrid .

    已编辑

    更新的代码应该处理大多数具有 Span ,方向等的极端情况 . 请注意,如果将setSpanSizeLookup()与GridLayoutManager一起使用,则出于性能原因,建议设置setSpanIndexCacheEnabled() .

    注意,似乎使用StaggeredGrid,似乎存在一个错误,其中子项的索引变得古怪且难以跟踪,因此下面的代码可能无法很好地使用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);
      }
    }
    

    希望能帮助到你 .

  • 317

    以下代码将处理StaggeredGridLayoutManager,GridLayoutManager和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;
        }
    }
    

    然后使用它

    mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));
    
  • 1

    这是我使用它的 doesn't require "spanCount" (列数)的解决方案,因为我使用 GridAutofitLayoutManager (根据所需的单元格大小计算列数)

    (请注意,这只适用于 GridLayoutManager

    public class GridSpacesItemDecoration extends RecyclerView.ItemDecoration {
        private final boolean includeEdge;
        private int spacing;
    
    
        public GridSpacesItemDecoration(int spacing, boolean includeEdge) {
            this.spacing = spacing;
            this.includeEdge = includeEdge;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (parent.getLayoutManager() instanceof GridLayoutManager) {
                GridLayoutManager layoutManager = (GridLayoutManager)parent.getLayoutManager();
                int spanCount = layoutManager.getSpanCount();
                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
                    }
                }
    
            }
    
        }
    }
    

    这是 GridAutofitLayoutManager 是有兴趣的人:

    public class GridAutofitLayoutManager extends GridLayoutManager {
        private int mColumnWidth;
        private boolean mColumnWidthChanged = true;
    
        public GridAutofitLayoutManager(Context context, int columnWidth)
        {
            /* Initially set spanCount to 1, will be changed automatically later. */
            super(context, 1);
            setColumnWidth(checkedColumnWidth(context, columnWidth));
        }
    
        public GridAutofitLayoutManager(Context context,int unit, int columnWidth)
        {
            /* Initially set spanCount to 1, will be changed automatically later. */
            super(context, 1);
            int pixColumnWidth = (int) TypedValue.applyDimension(unit, columnWidth, context.getResources().getDisplayMetrics());
            setColumnWidth(checkedColumnWidth(context, pixColumnWidth));
        }
    
        public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout)
        {
            /* Initially set spanCount to 1, will be changed automatically later. */
            super(context, 1, orientation, reverseLayout);
            setColumnWidth(checkedColumnWidth(context, columnWidth));
        }
    
        private int checkedColumnWidth(Context context, int columnWidth)
        {
            if (columnWidth <= 0)
            {
                /* Set default columnWidth value (48dp here). It is better to move this constant
                to static constant on top, but we need context to convert it to dp, so can't really
                do so. */
                columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                        context.getResources().getDisplayMetrics());
            }
            return columnWidth;
        }
    
        public void setColumnWidth(int newColumnWidth)
        {
            if (newColumnWidth > 0 && newColumnWidth != mColumnWidth)
            {
                mColumnWidth = newColumnWidth;
                mColumnWidthChanged = true;
            }
        }
    
        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
        {
            int width = getWidth();
            int height = getHeight();
            if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0)
            {
                int totalSpace;
                if (getOrientation() == VERTICAL)
                {
                    totalSpace = width - getPaddingRight() - getPaddingLeft();
                }
                else
                {
                    totalSpace = height - getPaddingTop() - getPaddingBottom();
                }
                int spanCount = Math.max(1, totalSpace / mColumnWidth);
                setSpanCount(spanCount);
    
                mColumnWidthChanged = false;
            }
            super.onLayoutChildren(recycler, state);
        }
    }
    

    Finally:

    mDevicePhotosView.setLayoutManager(new GridAutofitLayoutManager(getContext(), getResources().getDimensionPixelSize(R.dimen.item_size)));
    mDevicePhotosView.addItemDecoration(new GridSpacesItemDecoration(Util.dpToPx(getContext(), 2),true));
    
  • 7

    如果你想 FIXED 所有设备中 RecyclerView 项目的大小 . 你可以这样做

    public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
    
        private int mSpanCount;
        private float mItemSize;
    
        public GridSpacingItemDecoration(int spanCount, int itemSize) {
            this.mSpanCount = spanCount;
            mItemSize = itemSize;
        }
    
        @Override
        public void getItemOffsets(final Rect outRect, final View view, RecyclerView parent,
                RecyclerView.State state) {
            final int position = parent.getChildLayoutPosition(view);
            final int column = position % mSpanCount;
            final int parentWidth = parent.getWidth();
            int spacing = (int) (parentWidth - (mItemSize * mSpanCount)) / (mSpanCount + 1);
            outRect.left = spacing - column * spacing / mSpanCount;
            outRect.right = (column + 1) * spacing / mSpanCount;
    
            if (position < mSpanCount) {
                outRect.top = spacing;
            }
            outRect.bottom = spacing;
        }
    }
    

    recyclerview_item.xml

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="@dimen/recycler_view_item_width" 
        ...
        >
        ...
    </LinearLayout>
    

    dimens.xml

    <dimen name="recycler_view_item_width">60dp</dimen>
    

    Activity

    int numberOfColumns = 3;
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns));
    mRecyclerView.setAdapter(...);
    mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3,
            getResources().getDimensionPixelSize(R.dimen.recycler_view_item_width)));
    

    enter image description here

    enter image description here

  • 0

    只有一个简单的解决方案,您可以在任何需要的地方记住并实施 . 没有错误,没有疯狂的计算 . 将保证金放入卡/项目布局并将与填充相同的大小放入RecyclerView:

    item_layout.xml

    <CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:margin="10dp">
    

    activity_layout.xml

    <RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"/>
    
  • 75

    上面的答案阐明了设置边距处理GridLayoutManager和LinearLayoutManager的方法 .

    但是对于StaggeredGridLayoutManager,Pirdad Sakhizada的回答说:“它可能不适用于StaggeredGridLayoutManager” . 这应该是关于IndexOfSpan的问题 .

    你可以通过这种方式得到它:

    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();
        }
    }
    
  • 0

    对于这个问题,有一个非常简单而灵活的解决方案,只使用适用于每个LayoutManager的XML .

    假设你想要一个相等的X间距(例如8dp) .

    • 将CardView项目换行到另一个布局中

    • 给外部布局填充X / 2(4dp)

    • 使外部布局背景透明

    ...

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@android:color/transparent"
        android:padding="4dip">
    
        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v7.widget.CardView>
    
    </LinearLayout>
    
    • 为您的RecyclerView填充X / 2(4dp)

    ...

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="4dp" />
    

    就是这样 . 你有完美的X(8dp)间距 .

  • 25
    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) {
            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
            int column = params.getSpanIndex();
    
            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
                }
            }
        }
    }
    

    与edwardaa的答案略有不同,区别在于如何确定列,因为在诸如具有不同高度的项目的情况下,列不能仅通过% spanCount 来确定

  • 1

    这是我对 SpacesItemDecoration 的修改,可以采用 numOfColumsspace equally on top, bottom, left and right .

    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
        private int space;
        private int mNumCol;
    
        public SpacesItemDecoration(int space, int numCol) {
            this.space = space;
            this.mNumCol=numCol;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view,
                                   RecyclerView parent, RecyclerView.State state) {
    
            //outRect.right = space;
            outRect.bottom = space;
            //outRect.left = space;
    
            //Log.d("ttt", "item position" + parent.getChildLayoutPosition(view));
            int position=parent.getChildLayoutPosition(view);
    
            if(mNumCol<=2) {
                if (position == 0) {
                    outRect.left = space;
                    outRect.right = space / 2;
                } else {
                    if ((position % mNumCol) != 0) {
                        outRect.left = space / 2;
                        outRect.right = space;
                    } else {
                        outRect.left = space;
                        outRect.right = space / 2;
                    }
                }
            }else{
                if (position == 0) {
                    outRect.left = space;
                    outRect.right = space / 2;
                } else {
                    if ((position % mNumCol) == 0) {
                        outRect.left = space;
                        outRect.right = space/2;
                    } else if((position % mNumCol) == (mNumCol-1)){
                        outRect.left = space/2;
                        outRect.right = space;
                    }else{
                        outRect.left=space/2;
                        outRect.right=space/2;
                    }
                }
    
            }
    
            if(position<mNumCol){
                outRect.top=space;
            }else{
                outRect.top=0;
            }
            // Add top margin only for the first item to avoid double space between items
            /*
            if (parent.getChildLayoutPosition(view) == 0 ) {
    
            } else {
                outRect.top = 0;
            }*/
        }
    }
    

    并在你的逻辑上使用下面的代码 .

    recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol));
    
  • 1

    这个Link适合我所有情况你可以试试这个 .

  • 1

    我根据edwardaa的精彩答案制作的Kotlin版本

    class RecyclerItemDecoration(private val spanCount: Int, private val spacing: Int) : RecyclerView.ItemDecoration() {
    
      override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    
        val spacing = Math.round(spacing * parent.context.resources.displayMetrics.density)
        val position = parent.getChildAdapterPosition(view)
        val column = position % spanCount
    
        outRect.left = spacing - column * spacing / spanCount
        outRect.right = (column + 1) * spacing / spanCount
    
        outRect.top = if (position < spanCount) spacing else 0
        outRect.bottom = spacing
      }
    
    }
    
  • 0

    选择的答案几乎是完美的,但根据空间,项目宽度可能不相等 . (就我而言,这很关键) . 所以我最终得到了这个代码,增加了一点空间,所以项目的宽度都相同 .

    class GridSpacingItemDecoration(private val columnCount: Int, @Px preferredSpace: Int, private val includeEdge: Boolean): RecyclerView.ItemDecoration() {
    
    /**
     * In this algorithm space should divide by 3 without remnant or width of items can have a difference
     * and we want them to be exactly the same
     */
    private val space = if (preferredSpace % 3 == 0) preferredSpace else (preferredSpace + (3 - preferredSpace % 3))
    
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
        val position = parent.getChildAdapterPosition(view)
    
        if (includeEdge) {
    
            when {
                position % columnCount == 0 -> {
                    outRect.left = space
                    outRect.right = space / 3
                }
                position % columnCount == columnCount - 1 -> {
                    outRect.right = space
                    outRect.left = space / 3
                }
                else -> {
                    outRect.left = space * 2 / 3
                    outRect.right = space * 2 / 3
                }
            }
    
            if (position < columnCount) {
                outRect.top = space
            }
    
            outRect.bottom = space
    
        } else {
    
            when {
                position % columnCount == 0 -> outRect.right = space * 2 / 3
                position % columnCount == columnCount - 1 -> outRect.left = space * 2 / 3
                else -> {
                    outRect.left = space / 3
                    outRect.right = space / 3
                }
            }
    
            if (position >= columnCount) {
                outRect.top = space
            }
        }
    }
    

    }

  • 1

    对于那些有staggeredLayoutManager(如https://imgur.com/XVutH5u)问题的人

    recyclerView的方法:

    getChildAdapterPosition(view)
    getChildLayoutPosition(view)
    

    有时返回-1作为索引,所以我们可能会遇到设置itemDecor的麻烦 . 我的解决方案是覆盖已弃用的ItemDecoration方法:

    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
    

    而不是新手:

    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    

    像这样:

    recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
                @Override
                public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
                    TheAdapter.VH vh = (TheAdapter.VH) recyclerView.findViewHolderForAdapterPosition(itemPosition);
                    View itemView = vh.itemView;    //itemView is the base view of viewHolder
                    //or instead of the 2 lines above maybe it's possible to use  View itemView = layoutManager.findViewByPosition(itemPosition)  ... NOT TESTED
    
                    StaggeredGridLayoutManager.LayoutParams itemLayoutParams = (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams();
    
                    int spanIndex = itemLayoutParams.getSpanIndex();
    
                    if (spanIndex == 0)
                        ...
                    else
                        ...
                }
            });
    

    到目前为止似乎对我有用:)

  • 5

    我最终为我的 RecyclerView with GridLayoutManager and HeaderView 做了这样的事情 .

    在下面的代码中,我在每个项目之间设置了4dp空间(每个项目周围2dp,整个recyclerview周围2dp填充) .

    layout.xml

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycleview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="2dp" />
    

    片段/活动

    GridLayoutManager manager = new GridLayoutManager(getContext(), 3);
    recyclerView.setLayoutManager(manager);
    int spacingInPixels = Utils.dpToPx(2);
    recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));
    

    SpaceItemDecoration.java

    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
    
        private int mSpacing;
    
        public SpacesItemDecoration(int spacing) {
            mSpacing = spacing;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
            outRect.left = mSpacing;
            outRect.top = mSpacing;
            outRect.right = mSpacing;
            outRect.bottom = mSpacing;
        }
    }
    

    Utils.java

    public static int dpToPx(final float dp) {
        return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
    }
    
  • 4

    谢谢edwardaa的回答https://stackoverflow.com/a/30701422/2227031

    另一点需要注意的是:

    如果总间距和总itemWidth不等于屏幕宽度,则还需要调整itemWidth,例如,在适配器onBindViewHolder方法上

    Utils.init(_mActivity);
    int width = 0;
    if (includeEdge) {
        width = ScreenUtils.getScreenWidth() - spacing * (spanCount + 1);
    } else {
        width = ScreenUtils.getScreenWidth() - spacing * (spanCount - 1);
    }
    int itemWidth = width / spanCount;
    
    ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) holder.imageViewAvatar.getLayoutParams();
    // suppose the width and height are the same
    layoutParams.width = itemWidth;
    layoutParams.height = itemWidth;
    holder.imageViewAvatar.setLayoutParams(layoutParams);
    
  • 4

    制作https://stackoverflow.com/a/29905000/1649371(上面)解决方案工作我不得不修改以下方法(以及所有后续调用)

    @SuppressWarnings("all")
    protected int getItemSpanSize(RecyclerView parent, View view, int childIndex) {
    
        RecyclerView.LayoutManager mgr = parent.getLayoutManager();
        if (mgr instanceof GridLayoutManager) {
            return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
        } else if (mgr instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).isFullSpan() ? spanCount : 1;
        } else if (mgr instanceof LinearLayoutManager) {
            return 1;
        }
    
        return -1;
    }
    
    @SuppressWarnings("all")
    protected int getItemSpanIndex(RecyclerView parent, View view, int childIndex) {
    
        RecyclerView.LayoutManager mgr = parent.getLayoutManager();
        if (mgr instanceof GridLayoutManager) {
            return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
        } else if (mgr instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
        } else if (mgr instanceof LinearLayoutManager) {
            return 0;
        }
    
        return -1;
    }
    
  • 0

    如果您有一个切换开关在列表到网格之间切换,请不要忘记在设置任何新项目装饰之前调用 recyclerView.removeItemDecoration() . 如果没有,那么间距的新计算将是不正确的 .


    像这样的东西 .

    recyclerView.removeItemDecoration(gridItemDecorator)
            recyclerView.removeItemDecoration(listItemDecorator)
            if (showAsList){
                recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
                recyclerView.addItemDecoration(listItemDecorator)
            }
            else{
                recyclerView.layoutManager = GridLayoutManager(this, spanCount)
                recyclerView.addItemDecoration(gridItemDecorator)
            }
    
  • 0

    这也适用于带有 Headers 的 RecyclerView .

    public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
    
        private int spanCount;
        private int spacing;
        private boolean includeEdge;
        private int headerNum;
    
        public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) {
            this.spanCount = spanCount;
            this.spacing = spacing;
            this.includeEdge = includeEdge;
            this.headerNum = headerNum;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view) - headerNum; // item position
    
            if (position >= 0) {
                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
                    }
                }
            } else {
                outRect.left = 0;
                outRect.right = 0;
                outRect.top = 0;
                outRect.bottom = 0;
            }
        }
        }
    }
    
  • 0
    class VerticalGridSpacingDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
    
      override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: State
      ) {
        val layoutManager = parent.layoutManager as? GridLayoutManager
        if (layoutManager == null || layoutManager.orientation != VERTICAL) {
          return super.getItemOffsets(outRect, view, parent, state)
        }
    
        val spanCount = layoutManager.spanCount
        val position = parent.getChildAdapterPosition(view)
        val column = position % spanCount
        with(outRect) {
          left = if (column == 0) 0 else spacing / 2
          right = if (column == spanCount.dec()) 0 else spacing / 2
          top = if (position < spanCount) 0 else spacing
        }
      }
    }
    

相关问题