【问题标题】:RecyclerView remove divider / decorator after the last itemRecyclerView 在最后一项之后删除分隔符/装饰器
【发布时间】:2018-02-23 05:50:39
【问题描述】:

我有一个非常简单的 RecyclerView。
这就是我设置分隔线的方式:

DividerItemDecoration itemDecorator = new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL);
itemDecorator.setDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.news_divider));
recyclerView.addItemDecoration(itemDecorator);

这是drawable/news_divider.xml:

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@color/white_two"/>
    <size android:height="1dp"/>
</shape>

问题是由于某种原因,分隔线不仅仅是在项目之间创建的。但也在最后一项之后。而且我只希望它在项目之间而不是在每个项目之后。

知道如何防止分隔线在最后一项之后显示吗?

【问题讨论】:

标签: android android-recyclerview divider


【解决方案1】:

这是一个 Kotlin 扩展类:

    fun RecyclerView.addItemDecorationWithoutLastItem() {

    if (layoutManager !is LinearLayoutManager)
        return

    addItemDecoration(DividerItemDecorator(context))
 }

这里是 DividerItemDecorator 类

class DividerItemDecorator(context: Context) : ItemDecoration() {
    private val mDivider: Drawable = ContextCompat.getDrawable(context, R.drawable.divider)!!
    override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        val dividerLeft = parent.paddingLeft
        val dividerRight = parent.width - parent.paddingRight
        val childCount = parent.childCount
        for (i in 0..childCount - 2) {
            val child = parent.getChildAt(i)
            val params = child.layoutParams as RecyclerView.LayoutParams
            val dividerTop = child.bottom + params.bottomMargin
            val dividerBottom = dividerTop + mDivider.intrinsicHeight
            mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom)
            mDivider.draw(canvas)
        }
    }
}

这里是divider.xml

  <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:width="1dp"
        android:height="1dp" />
    <solid android:color="@color/your_color" />
</shape>

最后这样称呼它

recyclerView.addItemDecorationWithoutLastItem()

【讨论】:

    【解决方案2】:

    我在 DividerItemDecoration 的基础上添加了对垂直和水平方向(在 Kotlin 中)的支持,灵感来自此线程中先前的一些答案:

    class CustomDividerItemDecorator(private val divider: Drawable, private val orientation: Int) : RecyclerView.ItemDecoration() {
    private val bounds: Rect = Rect()
    
    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.layoutManager == null) {
            return
        }
        if (orientation == DividerItemDecoration.VERTICAL) {
            drawVertical(canvas, parent)
        } else {
            drawHorizontal(canvas, parent)
        }
    }
    
    private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
        canvas.save()
        val left: Int
        val right: Int
        if (parent.clipToPadding) {
            left = parent.paddingLeft
            right = parent.width - parent.paddingRight
            canvas.clipRect(
                left, parent.paddingTop, right, parent.height - parent.paddingBottom
            )
        } else {
            left = 0
            right = parent.width
        }
        val childCount = parent.childCount
        for (i in 0 until childCount - 1) {
            val child: View = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, bounds)
            val bottom: Int = bounds.bottom + child.translationY.roundToInt()
            val top = bottom - divider.intrinsicHeight
            divider.setBounds(left, top, right, bottom)
            divider.draw(canvas)
        }
        canvas.restore()
    }
    
    private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
        canvas.save()
        val top: Int
        val bottom: Int
        if (parent.clipToPadding) {
            top = parent.paddingTop
            bottom = parent.height - parent.paddingBottom
            canvas.clipRect(
                parent.paddingLeft, top, parent.width - parent.paddingRight, bottom
            )
        } else {
            top = 0
            bottom = parent.height
        }
        val childCount = parent.childCount
        for (i in 0 until childCount - 1) {
            val child: View = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, bounds)
            val right: Int = bounds.right + child.translationX.roundToInt()
            val left = right - divider.intrinsicWidth
            divider.setBounds(left, top, right, bottom)
            divider.draw(canvas)
        }
        canvas.restore()
    }
    
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
            outRect.setEmpty()
        } else if (orientation == DividerItemDecoration.VERTICAL) {
            outRect.set(0, 0, 0, divider.intrinsicHeight)
        } else {
            outRect.set(0, 0, divider.intrinsicWidth, 0)
        }
    }
    

    }

    用法:

        val dividerItemDecoration = CustomDividerItemDecorator(
            ContextCompat.getDrawable(requireContext(), R.drawable.<DRAWABLE NAME>)!!,
            DividerItemDecoration.HORIZONTAL
        )
        recyclerView.addItemDecoration(dividerItemDecoration)
    

    【讨论】:

      【解决方案3】:

      尝试将此项目装饰器设置为您的 RecyclerView

      class NoLastItemDividerDecorator(
          val context: Context,
          orientation: Int
      ) : DividerItemDecoration(context, orientation) {
      
          override fun getItemOffsets(
              outRect: Rect,
              view: View,
              parent: RecyclerView,
              state: RecyclerView.State
          ) {
              super.getItemOffsets(outRect, view, parent, state)
      
              val position = parent.getChildAdapterPosition(view)
              val last = parent.adapter?.itemCount ?: 0
      
              if (position == last - 1) {
                  outRect.set(0, 0, 0, 0)
              } else {
                  setDrawable(
                      ContextCompat.getDrawable(
                          context,
                          R.drawable.your_divider_shape
                      )
                  )
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        如果您的object: List&lt;class&gt; 中有id 属性,则可以通过将id 与列表中的lastIndex 进行比较,使用data binding 轻松删除最后一个分隔符,如果id是按照列表索引设置的。

        在你的ViewModel

        var lastIndexOfList get() = List.lastIndex
        

        分隔符 XML:

        <layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto" >
        
            <data>
                <import type="android.view.View" />
                <variable
                    name="Item"
                    type="com.example.appName.Item" />
                <variable
                    name="viewModel"
                    type="com.example.appName.ViewModel" />
            </data>
        
            ...
        
            <View
                android:id="@+id/divider"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@android:color/darker_gray"
                android:visibility="@{ item.id == viewModel.lastIndexOfList ? View.GONE : View.VISIBLE }" />
        
            ...
        
        </layout>
        

        【讨论】:

        • 尽量用 1px 代替 1dp,否则 1dp 在某些设备上会变得不可见
        【解决方案5】:

        如果您不喜欢在后面绘制分隔线,您可以简单地复制或扩展DividerItemDecoration 类并通过将for (int i = 0; i &lt; childCount; i++) 修改为for (int i = 0; i &lt; childCount - 1; i++) 来更改其绘制行为

        然后将你的装饰器添加为recyclerView.addItemDecoration(your_decorator);


        以前的解决方案:

        按照here 的建议,您可以像这样扩展 DividerItemDecoration:

        recyclerView.addItemDecoration(
            new DividerItemDecoration(context, linearLayoutManager.getOrientation()) {
                @Override
                public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
                    int position = parent.getChildAdapterPosition(view);
                    // hide the divider for the last child
                    if (position == state.getItemCount() - 1) {
                        outRect.setEmpty();
                    } else {
                        super.getItemOffsets(outRect, view, parent, state);
                    }
                }
            }
        );
        

        @Rebecca Hsieh 指出:

        当您在 RecyclerView 中的项目视图没有透明背景时,这有效,例如,

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:background="#ffffff">
            ... 
        </LinearLayout>
        

        RecyclerView 调用DividerItemDecoration.getItemOffsets 来测量子位置。此解决方案会将最后一个分隔符放在最后一项后面。因此 RecyclerView 中的项目视图应该有一个背景来覆盖最后一个分隔线,这使它看起来像隐藏了。

        【讨论】:

        • 此解决方案比公认的解决方案更干净,因为它依赖于适配器位置而不是子索引。此外,为了使其可重用,将匿名内部类替换为公共子类,例如public class MiddleDividerItemDecoration extends DividerItemDecoration{....
        • 如果您想设置默认分隔符,上述解决方案是可以的。如果您需要更多自定义,例如分隔线大小和颜色,则必须创建自己的分隔线,例如接受的答案@lwo Banas
        • 不知道为什么,但是 outRect.setEmpty() 对我不起作用。
        • @MaksimTuraev 我不明白你的代码。如果 .setEmpty() 将 Rect 设置为 0,0,0,0,那为什么它只适用于非透明背景?
        • @Ruben2112,最后一项上的 setEmpty() 表示不应移动它(无填充)。由于在它下面绘制了分隔线,如果最后一个项目没有移动,它会隐藏最后一个分隔线。除非它有透明背景。
        【解决方案6】:

        Kotlin 版本和 更新了 AbdulAli 工作答案的原始 DividerItemDecorator 类的新签名函数

        class DividerItemDecorator(private val mDivider: Drawable) : ItemDecoration() {
            private val mBounds: Rect = Rect()
        
            override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
                canvas.save()
                val left: Int
                val right: Int
                if (parent.clipToPadding) {
                    left = parent.paddingLeft
                    right = parent.width - parent.paddingRight
                    canvas.clipRect(
                        left, parent.paddingTop, right,
                        parent.height - parent.paddingBottom
                    )
                } else {
                    left = 0
                    right = parent.width
                }
                val childCount = parent.childCount
                for (i in 0 until childCount - 1) {
                    val child: View = parent.getChildAt(i)
                    parent.getDecoratedBoundsWithMargins(child, mBounds)
                    val bottom: Int = mBounds.bottom + Math.round(child.getTranslationY())
                    val top = bottom - mDivider.intrinsicHeight
                    mDivider.setBounds(left, top, right, bottom)
                    mDivider.draw(canvas)
                }
                canvas.restore()
            }
        
            override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
                if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
                    outRect.setEmpty()
                } else outRect.set(0, 0, 0, mDivider.intrinsicHeight)
            }
        }
        

        【讨论】:

          【解决方案7】:

          这是接受的答案的 Kotlin 版本:

          class DividerItemDecorator(private val divider: Drawable?) : RecyclerView.ItemDecoration() {
          
              override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
                  val dividerLeft = parent.paddingLeft
                  val dividerRight = parent.width - parent.paddingRight
                  val childCount = parent.childCount
                  for (i in 0..childCount - 2) {
                      val child: View = parent.getChildAt(i)
                      val params =
                          child.layoutParams as RecyclerView.LayoutParams
                      val dividerTop: Int = child.bottom + params.bottomMargin
                      val dividerBottom = dividerTop + (divider?.intrinsicHeight?:0)
                      divider?.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom)
                      divider?.draw(canvas)
                  }
              }
          }
          

          【讨论】:

            【解决方案8】:

            试试这个代码,它不会显示最后一项的分隔符。这种方法可以让您更好地控制绘图分隔线。

            public class DividerItemDecorator extends RecyclerView.ItemDecoration {
                private Drawable mDivider;
            
                public DividerItemDecorator(Drawable divider) {
                    mDivider = divider;
                }
            
                @Override
                public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
                    int dividerLeft = parent.getPaddingLeft();
                    int dividerRight = parent.getWidth() - parent.getPaddingRight();
            
                    int childCount = parent.getChildCount();
                    for (int i = 0; i <= childCount - 2; i++) {
                        View child = parent.getChildAt(i);
            
                        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            
                        int dividerTop = child.getBottom() + params.bottomMargin;
                        int dividerBottom = dividerTop + mDivider.getIntrinsicHeight();
            
                        mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
                        mDivider.draw(canvas);
                    }
                }
            }
            

            divider.xml:

            <?xml version="1.0" encoding="utf-8"?>
            <shape xmlns:android="http://schemas.android.com/apk/res/android"
                android:shape="rectangle">
                <size
                    android:width="1dp"
                    android:height="1dp" />
                <solid android:color="@color/grey_300" />
            </shape>
            

            像这样设置您的分隔线

            RecyclerView.ItemDecoration dividerItemDecoration = new DividerItemDecorator(ContextCompat.getDrawable(context, R.drawable.divider));
            recyclerView.addItemDecoration(dividerItemDecoration);
            

            【讨论】:

            • 赞成,但不应该是 i 还是 i ?
            • 如果不想为最后一项绘制分隔线,则必须设置其中一个。
            • 有什么办法可以减少with of divider?
            • 当然。通过调整ondraw方法中dividerLeft和dividerRight的值
            • 解决方案不起作用,但 onDrawOver 代替 onDraw 完成了工作
            【解决方案9】:

            Kotlin 的扩展功能:

            fun RecyclerView.addItemDecorationWithoutLastDivider() {
            
                if (layoutManager !is LinearLayoutManager)
                    return
            
                addItemDecoration(object :
                    DividerItemDecoration(context, (layoutManager as LinearLayoutManager).orientation) {
            
                    override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
                        super.getItemOffsets(outRect, view, parent, state)
            
                        if (parent.getChildAdapterPosition(view) == state.itemCount - 1)
                            outRect.setEmpty()
                        else
                            super.getItemOffsets(outRect, view, parent, state)
                    }
                })
            }
            

            您可以轻松使用它:

            recyclerView.addItemDecorationWithoutLastDivider()
            

            【讨论】:

            • 如何设置分隔线的高度/宽度?​​
            • outRect.setEmpty() 没有为我删除分隔符
            【解决方案10】:

            这是Android支持的定制版本DividerItemDecoration忽略最后一项:

            https://gist.github.com/mohsenoid/8ffdfa53f0465533833b0b44257aa641

            主要区别在于:

            private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
                canvas.save()
                val left: Int
                val right: Int
            
                if (parent.clipToPadding) {
                    left = parent.paddingLeft
                    right = parent.width - parent.paddingRight
                    canvas.clipRect(left, parent.paddingTop, right,
                            parent.height - parent.paddingBottom)
                } else {
                    left = 0
                    right = parent.width
                }
            
                val childCount = parent.childCount
                for (i in 0 until childCount - 1) {
                    val child = parent.getChildAt(i)
                    parent.getDecoratedBoundsWithMargins(child, mBounds)
                    val bottom = mBounds.bottom + Math.round(child.translationY)
                    val top = bottom - mDivider!!.intrinsicHeight
                    mDivider!!.setBounds(left, top, right, bottom)
                    mDivider!!.draw(canvas)
                }
                canvas.restore()
            }
            
            private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
                canvas.save()
                val top: Int
                val bottom: Int
            
                if (parent.clipToPadding) {
                    top = parent.paddingTop
                    bottom = parent.height - parent.paddingBottom
                    canvas.clipRect(parent.paddingLeft, top,
                            parent.width - parent.paddingRight, bottom)
                } else {
                    top = 0
                    bottom = parent.height
                }
            
                val childCount = parent.childCount
                for (i in 0 until childCount - 1) {
                    val child = parent.getChildAt(i)
                    parent.layoutManager.getDecoratedBoundsWithMargins(child, mBounds)
                    val right = mBounds.right + Math.round(child.translationX)
                    val left = right - mDivider!!.intrinsicWidth
                    mDivider!!.setBounds(left, top, right, bottom)
                    mDivider!!.draw(canvas)
                }
                canvas.restore()
            }
            

            【讨论】:

            • 您的解决方案(在链接中)包含?,其中@NonNull 代表Java。
            • 这看起来非常像一些 Java 自动转换为 Kotlin。尤其是setDrawable方法...
            【解决方案11】:

            接受的答案不会为装饰分配空间,因为它不会覆盖getItemOffsets()

            我已经调整了支持库中的 DividerItemDecoration 以排除最后一项的装饰

            public class DividerItemDecorator extends RecyclerView.ItemDecoration {
            
                private Drawable mDivider;
                private final Rect mBounds = new Rect();
            
                public DividerItemDecorator(Drawable divider) {
                    mDivider = divider;
                }
            
                @Override
                public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
                    canvas.save();
                    final int left;
                    final int right;
                    if (parent.getClipToPadding()) {
                        left = parent.getPaddingLeft();
                        right = parent.getWidth() - parent.getPaddingRight();
                        canvas.clipRect(left, parent.getPaddingTop(), right,
                                parent.getHeight() - parent.getPaddingBottom());
                    } else {
                        left = 0;
                        right = parent.getWidth();
                    }
            
                    final int childCount = parent.getChildCount();
                    for (int i = 0; i < childCount - 1; i++) {
                        final View child = parent.getChildAt(i);
                        parent.getDecoratedBoundsWithMargins(child, mBounds);
                        final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
                        final int top = bottom - mDivider.getIntrinsicHeight();
                        mDivider.setBounds(left, top, right, bottom);
                        mDivider.draw(canvas);
                    }
                    canvas.restore();
                }
            
                @Override
                public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            
                    if (parent.getChildAdapterPosition(view) == state.getItemCount() - 1) {
                        outRect.setEmpty();
                    } else
                        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
                }
            }
            

            要应用装饰器,请使用

            RecyclerView.ItemDecoration dividerItemDecoration = new DividerItemDecorator(dividerDrawable);
            recyclerView.addItemDecoration(dividerItemDecoration);
            

            包含方向的来源可以在这里找到 https://gist.github.com/abdulalin/146f8ca42aa8322692b15663b8d508ff

            【讨论】:

            • 谢谢!刚刚发布在此工作答案的 Kotlin 版本下方(带有原始类的所有新签名功能)。
            【解决方案12】:

            创建您自己的 Divider 类 (Example here)

            在绘制分隔线的代码中,首先检查您是否正在为列表中的最后一项绘制分隔线。如果有,就不要画了。

            请注意,如果您覆盖OnDrawOver,它会在您的视图顶部绘制,包括滚动条等。最好坚持使用OnDraw。 Google 上有很多示例,但 this 是创建自己的装饰器的好教程。

            【讨论】:

            • 是的,onDrawOver 在某些设备上提供了不正常的结果。
            【解决方案13】:

            这是我在我的应用程序中使用的DividerDecorator 类,它删除了最后一项的底线。

            public class DividerDecorator extends RecyclerView.ItemDecoration {
                private Drawable mDivider;
            
                public DividerDecorator(Context context) {
                    mDivider = context.getResources().getDrawable(R.drawable.recyclerview_divider);
                }
            
                @Override
                public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
                    int left = parent.getPaddingLeft();
                    int right = parent.getWidth() - parent.getPaddingRight();
            
                    int childCount = parent.getChildCount();
                    for (int i = 0; i < childCount; i++) {
                        View child = parent.getChildAt(i);
            
                        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            
                        int top = child.getBottom() + params.bottomMargin;
                        int bottom = top + mDivider.getIntrinsicHeight();
            
                        mDivider.setBounds(left, top, right, bottom);
                        mDivider.draw(c);
                    }
                }
            }
            

            您可以使用以下代码将其设置为您的RecyclerView

            mRecyclerViewEvent.addItemDecoration(new DividerDecorator(context));
            

            这里是 recyclerview_divider.xml

            <size
                android:width="1dp"
                android:height="1dp" />
            
            <solid android:color="@color/DividerColor" />
            

            【讨论】:

            • 我支持你,但覆盖 onDrawOver 提供了不需要的结果。分频器经常行为不端。事实证明,onDraw 覆盖对我来说更好。
            • onDrawOver 的问题是它总是滚动时缺少的最后一个 可见 分隔符。
            猜你喜欢
            • 2012-12-21
            • 1970-01-01
            • 1970-01-01
            • 2018-03-10
            • 1970-01-01
            • 2013-01-17
            • 1970-01-01
            • 2011-04-26
            • 1970-01-01
            相关资源
            最近更新 更多