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<class>
中有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 < 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_
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 的最后一项
将视图保留在最后一项 - LoadMore RecyclerView