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

RecyclerView remove divider / decorator after the last item

我有一个非常简单的 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>

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

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

创建您自己的分隔线class (Example here)

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

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

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

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" />

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

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

如果您不喜欢分隔线被绘制在后面,您可以简单地复制或扩展 DividerItemDecoration class 并通过将 for (int i = 0; i < childCount; i++) 修改为 for (int i = 0; i < 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>

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

已接受的答案不会分配 space 用于装饰,因为它不会覆盖 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

这是 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()
}

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

这是 接受的答案 的 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)
        }
    }
}

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

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

如果您的 object: List<class> 中有 id 属性,那么可以使用 data binding 轻松删除 最后一个分隔符 如果 id 是根据列表索引设置的,则将 id 与列表的 lastIndex 进行比较。

在你的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>

尝试将此项目装饰器设置为您的 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
                )
            )
        }
    }
}

我在 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)

这是一个 Kotlin 扩展 Class:

    fun RecyclerView.addItemDecorationWithoutLastItem() {

    if (layoutManager !is LinearLayoutManager)
        return

    addItemDecoration(DividerItemDecorator(context))
 }

这是 DividerItemDecorator Class

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