RecyclerView remove divider / decorator after the last item

AndroidAndroid RecyclerviewDivider

Android Problem Overview


I have a quite simple RecyclerView.
This is how I set the divider:

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

And this is 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>

The problem is for some reason the divider is not just created in between the items. But also after the last item. And I want it only in between the items not after every item.

Any idea how to prevent the divider from showing after the last item?

Android Solutions


Solution 1 - Android

Try this Code, it won't show divider for the last item. This method will give you more control over drawing divider.

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>

Set your Divider like this:

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

Solution 2 - Android

If you don't like divider being drawn behind, you can simply copy or extend DividerItemDecoration class and change its drawing behaviour by modifying for (int i = 0; i < childCount; i++) to for (int i = 0; i < childCount - 1; i++)

Then add your decorator as recyclerView.addItemDecoration(your_decorator);


PREVIOUS SOLUTION:

As proposed here you can extend DividerItemDecoration like this:

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 pointed out:

This works when your item view in RecyclerView doesn't have a transparent background, for example,

<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 is called by RecyclerView to measure the child position. This solution will put the last divider behind the last item. Therefore the item view in RecyclerView should have a background to cover the last divider and this makes it look like hidden.

Solution 3 - Android

Here is Kotlin version of accepted answer :

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

Solution 4 - Android

The accepted answer doesn't allocate space for decoration as it does not override getItemOffsets()

I have tweaked the DividerItemDecoration from support library to exclude the decoration from the last item

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

To apply the decorator, use

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

The source for including orientation can be found here https://gist.github.com/abdulalin/146f8ca42aa8322692b15663b8d508ff

Solution 5 - Android

Extension function for 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)
        }
    })
}

You can use it easily:

recyclerView.addItemDecorationWithoutLastDivider()

Solution 6 - Android

Here's the DividerDecorator class i use in my apps which removes the bottom line of last item.

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

You can set it to your RecyclerView with the following code:

mRecyclerViewEvent.addItemDecoration(new DividerDecorator(context));

Here's the recyclerview_divider.xml

http://schemas.android.com/apk/res/android" android:shape="rectangle">

<size
    android:width="1dp"
    android:height="1dp" />

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

Solution 7 - Android

Kotlin version and updated with new signature functions of the original DividerItemDecorator class of the working answer by 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)
    }
}

Solution 8 - Android

Create your own Divider class (Example here)

In the code that draws the divider, check first if you are drawing the divider for the last item in the list. If so, don't draw it.

Just be aware that if you override OnDrawOver it draws on TOP of your view including scrollbars etc. Best to stick to OnDraw. Lots of examples on Google but this is a good tutorial on creating your own decorators.

Solution 9 - Android

I added support for both vertical and horizontal orientation (in Kotlin) based on DividerItemDecoration, inspired by some of the previous answers in this thread:

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

}

Usage:

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

Solution 10 - Android

Here is a Kotlin Extension Class:

    fun RecyclerView.addItemDecorationWithoutLastItem() {

    if (layoutManager !is LinearLayoutManager)
        return

    addItemDecoration(DividerItemDecorator(context))
 }

Here is the 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)
        }
    }
}

Here is the 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>

And finally call it like this

recyclerView.addItemDecorationWithoutLastItem()

Solution 11 - Android

This is a customized version of Android support DividerItemDecoration which ignore the last item:

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

main difference is:

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

Solution 12 - Android

If you have an id property in your object: List<class> then the last divider can easily be removed with data binding by comparing the id with the lastIndex of the list if the id is set in accordance with list indexes.

In your ViewModel:

var lastIndexOfList get() = List.lastIndex

Divider 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>

Solution 13 - Android

Try to set this item decorator to your 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
                )
            )
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSlobodan AntonijevićView Question on Stackoverflow
Solution 1 - AndroidBhuvanesh BSView Answer on Stackoverflow
Solution 2 - AndroidMaksim TuraevView Answer on Stackoverflow
Solution 3 - Androidsma6871View Answer on Stackoverflow
Solution 4 - AndroidAbdulAliView Answer on Stackoverflow
Solution 5 - Androidseyfullah.bilginView Answer on Stackoverflow
Solution 6 - AndroidsavepopulationView Answer on Stackoverflow
Solution 7 - AndroidCyrilView Answer on Stackoverflow
Solution 8 - AndroidKuffsView Answer on Stackoverflow
Solution 9 - AndroidCqretView Answer on Stackoverflow
Solution 10 - AndroidShogun NassarView Answer on Stackoverflow
Solution 11 - AndroidMohsen MirhoseiniView Answer on Stackoverflow
Solution 12 - AndroidRonnieView Answer on Stackoverflow
Solution 13 - AndroidDenys LoburView Answer on Stackoverflow