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


77

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

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

任何想法如何防止分隔线显示在最后一项之后?


1
不要使用默认的分隔线。您可以在“回收者视图” xml项目中添加分隔线,并根据需要显示或隐藏。
james

发布您的分隔器项目装饰器代码。
Bhuvanesh BS

Answers:


120

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

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

5
已支持,但不应该是i <= childCount-2i <childCount-1吗?
SlobodanAntonijević17年

如果不想为最后一项绘制分隔线,则必须设置其中一项。
Bhuvanesh BS

1
有什么办法可以减少分频器?
GiedriusŠlikas18年

2
当然。通过在提取方法中调整dividerLeft和dividerRight的值
Bhuvanesh BS

2
该解决方案没有奏效,但onDrawOver不是onDraw做的工作
Przemo

63

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

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


上一个解决方案

按照此处的建议您可以像这样扩展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中的项目视图应具有覆盖最后一个分隔线的背景,这使其看起来像是隐藏的。


1
此解决方案比公认的解决方案更干净,因为它依赖于适配器位置而不是子索引。另外,要使其可重用,请使用公共子类(例如)替换匿名内部类public class MiddleDividerItemDecoration extends DividerItemDecoration{...
Iwo Banas

1
如果您想设置默认的分频器,上述解决方案是可以的。如果您需要更多的自定义设置,例如分隔线的大小和颜色,则必须创建自己的分隔线,例如已接受的答案@lwo Banas
Bhuvanesh BS

10
不知道为什么,但是outRect.setEmpty()对我不起作用。
lostintranslation

2
@MaksimTuraev我不理解您的代码。如果.setEmpty()将Rect设置为0,0,0,0,那么为什么它只能在非透明背景下工作?
Ruben2112 '18八月

1
@ Ruben2112,最后一项的setEmpty()表示不应移动它(不填充)。在分隔线下绘制分隔线时,如果最后一个项目不移动,则会隐藏最后一个分隔线。除非它具有透明的背景。
Glacoon

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


最后一个工作的,谢谢!
Burak

5

这是公认的答案的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)
        }
    }
}

4

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


1
我投票给您,但是覆盖onDrawOver提供了不需要的结果。分频器不断地表现不良。事实证明onDraw替代对我来说更好。
SlobodanAntonijević17年

onDrawOver的问题在于,它将始终是滚动时缺少的最后一个可见分隔线。
Morten Holmgaard

3

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

如何设置分隔线的高度/宽度?
CoolMind

2

创建自己的Divider类(此处为示例

在绘制分隔线的代码中,首先检查是否要绘制列表中最后一项的分隔线。如果是这样,请不要绘制它。

请注意,如果您覆盖OnDrawOver它,则会使用视图的TOP,包括滚动条等。最好坚持使用OnDraw。对谷歌的例子很多,但是是在创建自己的装修一个很好的教程。


是的,onDrawOver在某些设备上提供了异常的结果。
SlobodanAntonijević17年

0

这是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中的立场。
CoolMind

1
看起来就像某些Java自动转换为Kotlin一样。特别是setDrawable方法...
Marc Plano-Lesay

-2

如果您已经设置使用:

    DividerItemDecoration dividerItemDecoration;
    dividerItemDecoration  = new DividerItemDecoration(context, 
    DividerItemDecoration.VERTICAL);
    mRecyclerView.addItemDecoration(dividerItemDecoration);

如果要删除:

    mRecyclerView.removeItemDecoration(dividerItemDecoration);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.