Skip to content

Instantly share code, notes, and snippets.

@johnwatsondev
Last active November 30, 2022 07:48
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save johnwatsondev/720730cf6b8c59fa6abe4f31dbaf59d7 to your computer and use it in GitHub Desktop.
Save johnwatsondev/720730cf6b8c59fa6abe4f31dbaf59d7 to your computer and use it in GitHub Desktop.
Customizing the DividerItemDecoration class so we can remove divider / decorator after the last item
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
/**
* DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider
* between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and
* {@link #VERTICAL} orientations.
*
* <pre>
* mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
* mLayoutManager.getOrientation());
* recyclerView.addItemDecoration(mDividerItemDecoration);
* </pre>
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private static final String TAG = "DividerItem";
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
/**
* Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
*/
private int mOrientation;
// private final Rect mBounds = new Rect();
private final boolean mIsShowInLastItem;
/**
* Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a
* {@link LinearLayoutManager}.
*
* @param context Current context, it will be used to access resources.
* @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
* @param isShowInLastItem Whether show the divider in last item.
*/
public DividerItemDecoration(Context context, int orientation, boolean isShowInLastItem) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
if (mDivider == null) {
Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
+ "DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
setOrientation(orientation);
this.mIsShowInLastItem = isShowInLastItem;
}
/**
* Sets the orientation for this divider. This should be called if
* {@link RecyclerView.LayoutManager} changes orientation.
*
* @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
*/
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException(
"Invalid orientation. It should be either HORIZONTAL or VERTICAL");
}
mOrientation = orientation;
}
/**
* Sets the {@link Drawable} for this divider.
*
* @param drawable Drawable that should be used as a divider.
*/
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
}
mDivider = drawable;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left;
final int right;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && 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();
}
int childCount;
if (mIsShowInLastItem) {
childCount = parent.getChildCount();
} else {
childCount = parent.getChildCount() - 1;
}
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
// parent.getDecoratedBoundsWithMargins(child, mBounds);
// final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
int decoratedBottom = parent.getLayoutManager().getDecoratedBottom(child);
final int bottom = decoratedBottom + Math.round(child.getTranslationY());
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
final int top;
final int bottom;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(parent.getPaddingLeft(), top,
parent.getWidth() - parent.getPaddingRight(), bottom);
} else {
top = 0;
bottom = parent.getHeight();
}
int childCount;
if (mIsShowInLastItem) {
childCount = parent.getChildCount();
} else {
childCount = parent.getChildCount() - 1;
}
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
// parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
// final int right = mBounds.right + Math.round(child.getTranslationX());
int decoratedRight = parent.getLayoutManager().getDecoratedRight(child);
final int right = decoratedRight + Math.round(child.getTranslationX());
final int left = right - mDivider.getIntrinsicWidth();
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 (mDivider == null) {
outRect.setEmpty();
return;
}
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
int itemCount = state.getItemCount();
if (mIsShowInLastItem) {
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
} else if (itemPosition == itemCount - 1) {
// We didn't set the last item when mIsShowInLastItem's value is false.
outRect.setEmpty();
} else {
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
}
@johnwatsondev
Copy link
Author

johnwatsondev commented Apr 19, 2018

The getClipToPadding() method in ViewGroup.java is added in API 21. So we refactor the drawVertical and drawHorizontal method.

@johnwatsondev
Copy link
Author

We add the mIsShowInLastItem variable to control the logic of showing divider in last item.

Copy link

ghost commented Dec 24, 2018

works perfect

@CoolMind
Copy link

Thanks! Can we use the code in commercial apps?

@mert-aydin
Copy link

Thanks a lot!!!

@johnwatsondev
Copy link
Author

Thanks! Can we use the code in commercial apps?

Sure. :)

@vickyKDV
Copy link

vickyKDV commented Jan 6, 2020

Thx A lot !!!,, helpfull

@wiztensai
Copy link

thanks!!

@CoolMind
Copy link

CoolMind commented Sep 24, 2020

I have been using this code for 2 years, but currently noticed that adding a picture can sometimes produce removing a space before the last item (and after the last), if isShowInLastItem == false. If use DividerItemDecoration, all spaces will be equal, but we will have the last space visible. I think,

    } else if (itemPosition == itemCount - 1) {
        // We didn't set the last item when isShowInLastItem's value is false.
        outRect.setEmpty();
    }

works wrong, but I am not sure. Every time I add a picture before plus sign (in a last but one position).

Screenshot_200

UPDATE

Sorry, I found a mistake in my code. When I added a picture, I used notifyItemInserted(position) to update a list. But in my case it was position = list.lastIndex (this is wrong). So, it made double refreshing and called outRect.setEmpty(); twice. By the way, after removing outRect.setEmpty(); all dividers were drawn the same way.

@bipinvaylu
Copy link

Thanks @johnwatsondev , I am using this in to Kotlin code and Here i had converted it to Kotlin: https://gist.github.com/bipinvaylu/2714d9d429dc72b0108c05be52b609e0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment