RecyclerView 在最后一项之后删除分隔符/装饰器

Posted

技术标签:

【中文标题】RecyclerView 在最后一项之后删除分隔符/装饰器【英文标题】:RecyclerView remove divider / decorator after the last item 【发布时间】: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:/>
</shape>

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

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

【问题讨论】:

不要使用默认分隔符。您在回收站视图 xml 项目中添加分隔符,并根据需要显示或隐藏。 发布您的分隔物品装饰器代码。 见gist.github.com/johnwatsondev/720730cf6b8c59fa6abe4f31dbaf59d7。 【参考方案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:
        android: />
    <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_
        android:layout_
        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_
    android:layout_
    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:
        android: />
    <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:
    android: />

<solid android:color="@color/DividerColor" />

【讨论】:

我支持你,但覆盖 onDrawOver 提供了不需要的结果。分频器经常行为不端。事实证明,onDraw 覆盖对我来说更好。 onDrawOver 的问题是它总是滚动时缺少的最后一个 可见 分隔符。

以上是关于RecyclerView 在最后一项之后删除分隔符/装饰器的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView分页不起作用

recyclerview 内的 RecyclerView 只显示父 recyclerview 的最后一项

将视图保留在最后一项 - LoadMore RecyclerView

RecyclerView 中使用 StaggeredGridLayout 的全宽页脚或最后一项

Recyclerview表示到达最后一项

RecyclerView 设置最后一个ArrayList项数据为第一项