【问题标题】:Decorating RecyclerView (with GridLayoutManager) to display divider between items装饰 RecyclerView(使用 GridLayoutManager)以显示项目之间的分隔符
【发布时间】:2015-03-19 14:07:57
【问题描述】:

装饰 RecyclerView 以获得这样的外观和感觉的最佳和最简单的方法是什么?

这里的主要挑战是只在项目之间设置分隔线,而不是在项目和屏幕的左/右边界之间。

有什么想法吗?

【问题讨论】:

  • developer.android.com/reference/android/support/v7/widget/…, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State)
  • @pskink 我看到有必要创建/重用某种物品装饰。问题是关于具体解决方案。
  • @pskink 在 getItemOffsets 方法中确定项目是否有右/左邻居的正确方法是什么?
  • RecyclerView.getChildPosition(View child) 给你item位置,位置决定item在RecyclerView上的布局位置
  • @pskink 如何理解它是否在边界附近(一般情况下)?

标签: android android-recyclerview


【解决方案1】:

我不知道你为什么需要它,但是这个 UI 很容易用 RecyclerView 装饰器实现。

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

您可以根据需要更改 photo_list_preview_columnsphotos_list_spacing

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

和装饰器(需要一些重构)

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

【讨论】:

  • 谢谢,德米特里!像魅力一样工作!
  • @AlexKorovyansky 相同的 UI 但加载更多项目如何实现?
  • 这是一个好的开始,但是当项目动画(即调用 notifyItemChanged)时,这会在底行下方画线
  • 如果可以,我会过来拥抱你,兄弟。这是我一直在寻找的东西。谢谢
  • mNeedLeftSpacing 状态变量是什么意思?它会在呼叫订购时中继吗?
【解决方案2】:

这是一个更简单、更用户友好的实现:

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

我还在设置outRect 之前进行检查,因为每个viewType 都有不同的spanSizes,我只需要为allowedViewTypes 添加一个额外的中间空格。您可以轻松删除该验证,并且代码会更简单。对我来说它看起来像这样:

【讨论】:

  • 谢谢。我取出 allowedViewTypes 和 is Media 并将 outRect.top 设置为 0 因为我只想要列之间的中间空间,而不是行之间的空间。
  • 这不适用于宽度 > 2 幅图像的网格,因为您不能只将间距除以 2,这取决于图像的位置。下面我的回答中有更多详细信息:stackoverflow.com/a/63488804/238166
  • 什么是item_imageitem_blur
【解决方案3】:

您可能会得到答案,但我仍在发布可以帮助他人的解决方案。这可以通过传递方向用于垂直、水平列表或网格视图。

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

【讨论】:

  • 在网格的行大小不同的情况下效果不佳
【解决方案4】:

另一个对我有用的简单解决方案。希望有用。

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

    }
}

【讨论】:

    【解决方案5】:

    这是我在 Kotlin 中的实现。我修改了其他答案的代码,以便为全跨项目显示分隔符。

    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
    }
    

    结果:

    【讨论】:

    • 如果在 0 位置有一个完整的跨度项怎么办?定义的方法 isInTheFirstRow、isFirstInRow、isLastInRow 似乎并不通用。它仅适用于您的特定用例,如图所示还是通用的?
    • 它应该是通用的,但你是对的,我认为 0 位置的全跨度项目没有被覆盖。不过我没试过。
    • 另外,这些装饰线如何设置自定义颜色?不适用于应用程序中的所有分隔符(android:listDivider),仅适用于这个 recyclerview 吗?有什么想法吗?
    • 您可以将&lt;item name="android:listDivider"&gt;@drawable/divider_grid&lt;/item&gt; 添加到您的 RecyclerView 的样式中。
    • 这将适用于应用程序中的所有分隔符,我们只需要为特定的装饰器申请。
    【解决方案6】:
      @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;
            }
    
        }
    
    }
    

    【讨论】:

    • 请考虑对代码的作用添加一些解释。纯代码回答可能从技术上回答问题,但不太可能对有类似问题的其他用户有用。
    【解决方案7】:

    下面是我的自定义类,它允许 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)
            }
        }
    }
    

    }

    要将其添加为 RecyclerView 中的项目装饰器,请添加以下行:

    /*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)))
    

    【讨论】:

      【解决方案8】:

      这是我的版本。基于Nicolay's answer,但改进为使用三个或更多图像的网格并使用 dp 单位作为间距。 (他的版本没有提供相同大小的图像/间距超过 2 个图像。)

      注意:计算每张图像间距的逻辑比将间距除以 2(每张图像一半)更复杂,大多数答案都没有考虑到这一点。

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

      这适用于来自Activity.onCreate() 的回收站视图,如下所示

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

      示例:

      【讨论】:

        【解决方案9】:

        如果你有标题使用这个。

        隐藏标题集skipHeaderDivider=false的分隔符,否则设置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
            }
        }
        

        【讨论】:

          【解决方案10】:

          你可以试试我的解决方案。如果您想要列表视图或网格视图,这很灵活。

              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
          }
          

          注意:这对于跨度计数为 2 的情况是特殊的

          【讨论】:

          • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
          • 不鼓励仅使用代码的答案,因此请在您的答案中添加一些解释。虽然该代码可能没问题,但如果您还解释它的作用、它如何回答问题以及它与所有现有答案的方法有何不同,则会更有帮助。
          猜你喜欢
          • 1970-01-01
          • 2018-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-01
          • 2016-03-09
          • 2017-06-26
          • 1970-01-01
          相关资源
          最近更新 更多