how to customize Tab indicator width ? - android

I want this type of tab indicator how i can achieve this, i have tried all solutions with drawable selectable handler but not getting anything

There is a simpler approach to achieve this by just providing a drawable of your custom indicator to the app:tabIndicator.
For example, in this case:
underline.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<item android:gravity="center">
<shape>
<size
android:width="22dp"
android:height="2dp" />
<corners android:radius="2dp" />
<solid android:color="#FF0000" />
</shape>
</item>
</layer-list>
and add it in your TabLayout in this way
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/_16dp"
android:layout_marginTop="#dimen/_16dp"
app:tabIndicator="#drawable/underline"
app:tabIndicatorColor="#color/offers_header_text"
app:tabIndicatorHeight="4dp"
app:tabMode="scrollable"
app:tabRippleColor="#null" />

Try this:
public void wrapTabIndicatorToTitle(TabLayout tabLayout, int externalMargin, int internalMargin) {
View tabStrip = tabLayout.getChildAt(0);
if (tabStrip instanceof ViewGroup) {
ViewGroup tabStripGroup = (ViewGroup) tabStrip;
int childCount = ((ViewGroup) tabStrip).getChildCount();
for (int i = 0; i < childCount; i++) {
View tabView = tabStripGroup.getChildAt(i);
//set minimum width to 0 for instead for small texts, indicator is not wrapped as expected
tabView.setMinimumWidth(0);
// set padding to 0 for wrapping indicator as title
tabView.setPadding(0, tabView.getPaddingTop(), 0, tabView.getPaddingBottom());
// setting custom margin between tabs
if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams();
if (i == 0) {
// left
settingMargin(layoutParams, externalMargin, internalMargin);
} else if (i == childCount - 1) {
// right
settingMargin(layoutParams, internalMargin, externalMargin);
} else {
// internal
settingMargin(layoutParams, internalMargin, internalMargin);
}
}
}
tabLayout.requestLayout();
}
}
private void settingMargin(ViewGroup.MarginLayoutParams layoutParams, int start, int end) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
layoutParams.setMarginStart(start);
layoutParams.setMarginEnd(end);
} else {
layoutParams.leftMargin = start;
layoutParams.rightMargin = end;
}
}
After setting the view pager in java file add :
wrapTabIndicatorToTitle(tabLayout,80,80);

One simple solution is:
tabLayout.setTabIndicatorFullWidth(false);
But it works with latest dependency like
implementation 'com.google.android.material:material:1.0.0'

For support library version 28.0.0, I have come up with a solution that replaces the TabLayout.tabViewContentBounds object with a custom one, so that you can apply your logic here to modify the indicator position anyway you want
here is the custom RectF class (thank goodness it's not a final class)
public class TabIndicatorRectF extends RectF implements Serializable {
private RectF temp = new RectF();
private IndicatorBoundsModifier indicatorBoundsModifier;
public TabIndicatorRectF(IndicatorBoundsModifier indicatorBoundsModifier) {
this.indicatorBoundsModifier = indicatorBoundsModifier;
}
private interface IndicatorBoundsModifier {
void modify(RectF bounds);
}
#Override
public void set(float left, float top, float right, float bottom) {
temp.set(left, top, right, bottom);
indicatorBoundsModifier.modify(temp);
super.set(temp);
}
public void replaceBoundsRectF(TabLayout tabLayout) {
try {
Field field = TabLayout.class.getDeclaredField("tabViewContentBounds");
field.setAccessible(true);
field.set(tabLayout, this);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static class FixedWidthModifier implements IndicatorBoundsModifier {
private float halfWidth;
public FixedWidthModifier(float width) {
this.halfWidth = Math.abs(width) / 2;
}
#Override
public void modify(RectF bounds) {
float centerX = bounds.centerX();
bounds.left = centerX - halfWidth;
bounds.right = centerX + halfWidth;
}
}
}
now call this
new TabIndicatorRectF(
new TabIndicatorRectF.FixedWidthModifier(
getResources().getDimension(R.dimen.tab_indicator_width)))
.replaceBoundsRectF(tabLayout);

Related

ExpandableListView - border around parent and its children

I have a view that uses the ExpandableListView that has a ton of logic around it and in the adapters. For e.g., it looks like this
I have a requirement to display the same view with a different skin that has the expand/collapse hidden and has a border around parent and its children, something like this
I see attributes to have border for the whole control or just parent or individual child but nothing to have a border around parent and its children.
Has anyone done something like this? Short of not using Expandablelistview and recreating the view, is there anyway I can achieve the border?
Edit 1:
Here is a gist that has the template for what I am trying to do.
Edit 2:
I have a solution playing with parent and child borders,
setting parent to ┎─┒
and all-but-last children to ┃ ┃
and last child to ┖─┚
Here is the gist for the solution I have so far
I am still open to a better solution and will offer the bounty to anything that is less kludge than my solution.
EDIT So I've added ItemDecoration feature to ExpandableListView, It's pretty much works like the RecyclerView's ItemDecoration, here is the code:
Subclass the ExpandableListView
public class ExpandableListViewItemDecoration extends ExpandableListView {
private List<ItemDecorationListView> itemDecorations = new ArrayList<>(1);
/* ... */
public void addItemDecoration(ItemDecorationListView item){
itemDecorations.add(item);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
final int count = itemDecorations.size();
for (int i = 0; i < count; i++) {
itemDecorations.get(i).onDrawOver(canvas, this);
}
}
ItemDecorationListView:
public abstract class ItemDecorationListView {
public void onDrawOver(Canvas c, ListView parent) {
}
}
The ItemDecorator:
public class ItemDecoratorBorderListView extends ItemDecorationListView {
private final Paint paint = new Paint();
private final int size;
public ItemDecoratorBorderListView(int size, #ColorInt int color) {
this.size = size;
paint.setColor(color);
paint.setStrokeWidth(size);
paint.setStyle(Paint.Style.STROKE);
}
public static final String TAG = ItemDecoratorBorderListView.class.getSimpleName();
#Override
public void onDrawOver(Canvas c, ListView parent) {
super.onDrawOver(c, parent);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (isHeader(child, parent, i)) {
for (int j = i + 1; j < childCount; j++) {
View childEnd = parent.getChildAt(j);
boolean end = isHeader(childEnd, parent, i) || j == childCount - 1;
if (end) {
if (BuildConfig.DEBUG) { Log.d(TAG, String.format(Locale.ENGLISH, "Draw called i: %d, j: %d", i, j)); }
childEnd = parent.getChildAt(j - 1);
if (j == childCount - 1) { childEnd = parent.getChildAt(j); }
float top = child.getTop() + child.getTranslationY() + size + child.getPaddingTop();
float bottom = childEnd.getBottom() + childEnd.getTranslationY() - size - childEnd
.getPaddingBottom();
float right = child.getRight() + child.getTranslationX() - size - child.getPaddingRight();
float left = child.getLeft() + child.getTranslationX() + size + child.getPaddingLeft();
c.drawRect(left, top, right, bottom, paint);
i = j - 1;
break;
}
}
}
}
}
public boolean isHeader(View child, ListView parent, int position) {
//You need to set an Id for your layout
return child.getId() == R.id.header;
}
}
And just add it to your ExpandableListView:
expandableList.addItemDecoration(new ItemDecoratorBorderListView(
getResources().getDimensionPixelSize(R.dimen.stroke_size),
Color.GRAY
));
Old Answer:
This is an implementation with RecyclerView and ItemDecoration, I've already written this solution before knowing you're stuck with legacy code, So I'm sharing this anyway.
Item Decoration:
public class ItemDecoratorBorder extends RecyclerView.ItemDecoration {
private final Paint paint = new Paint();
private final int size;
public ItemDecoratorBorder(int size, #ColorInt int color) {
this.size = size;
paint.setColor(color);
paint.setStrokeWidth(size);
paint.setStyle(Paint.Style.STROKE);
}
public static final String TAG = ItemDecoratorBorder.class.getSimpleName();
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getLayoutManager() == null) { return; }
int childCount = parent.getChildCount();
RecyclerView.LayoutManager lm = parent.getLayoutManager();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (isHeader(child, parent)) {
for (int j = i + 1; j < childCount; j++) {
View childEnd = parent.getChildAt(j);
boolean end = isHeader(childEnd, parent) || j == childCount - 1;
if (end) {
if (BuildConfig.DEBUG) { Log.d(TAG, String.format(Locale.ENGLISH, "Draw called i: %d, j: %d", i, j)); }
childEnd = parent.getChildAt(j - 1);
if (j == childCount - 1) {
childEnd = parent.getChildAt(j);
}
float top = child.getTop() + child.getTranslationY() + size + child.getPaddingTop();
float bottom = lm.getDecoratedBottom(childEnd) + childEnd.getTranslationY() - size - childEnd.getPaddingBottom();
float right = lm.getDecoratedRight(child) + child.getTranslationX() - size - child.getPaddingRight();
float left = lm.getDecoratedLeft(child) + child.getTranslationX() + size + child.getPaddingLeft();
c.drawRect(left, top, right, bottom, paint);
i = j - 1;
break;
}
}
}
}
}
public boolean isHeader(View child, RecyclerView parent) {
int viewType = parent.getLayoutManager().getItemViewType(child);
return viewType == R.layout.layout_header;
}
I'm finding where a group starts and ends using the view types and draw a rectangle around the start and end position.
The code is available at my github repo
Well I have a solution for you, but It's better to use recycleView instead of listView, However, We can draw line for every sides e.g:
for parent group it will be something like ┎─┒ and for all child's without the last child it will be something like: ┎ ┒ and for the last child it will be like : ──.
The code: `groupbg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:left="2dp" android:top="2dp" android:right="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
</layer-list>
normalchild.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:left="2dp" android:right="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
bottomchild.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:bottom="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
No set it to your adapter:
private int childrenCount;
#Override
public int getChildrenCount(int groupPosition) {
return childrenCount = data.get(groupPosition).getItems().length;
}
#Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View view;
if (convertView == null){
view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
}
else {
view = convertView;
}
view.setBackground(context.getResources().getDrawable(R.drawable.groupbg));
TextView lblNumber = view.findViewById(R.id.lblNumber);
TextView lblName = view.findViewById(R.id.lblName);
lblNumber.setText((groupPosition + 1) + ".");
lblName.setText(((TestModel)getGroup(groupPosition)).getCategory());
return view;
}
#Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View view;
if (convertView == null){
view = LayoutInflater.from(context).inflate(R.layout.item_child, parent, false);
}
else {
view = convertView;
}
TextView lblNumber = view.findViewById(R.id.lblNumber);
TextView lblName = view.findViewById(R.id.lblName);
lblNumber.setText((childPosition + 1)+ ".");
lblName.setText((String)getChild(groupPosition, childPosition));
if (childPosition < childrenCount)
view.setBackground(context.getResources().getDrawable(R.drawable.normalchild));
else view.setBackground(context.getResources().getDrawable(R.drawable.bottomchild));
return view;
}
You can try using this library. Custom RecyclerView that implement features like ExpandableListView.

Android - How to add a dashed/dotted separator line for RecyclerView?

I have used the code from this answer to create a solid separator line for my RecyclerViews.
However, I would like the line to be dashed/dotted.
I already have a line_dashed.xml resource that I am using elsewhere in my app:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<stroke
android:color="#color/blue"
android:dashGap="12dp"
android:dashWidth="12dp"
android:width="1dp" />
</shape>
But if I try applying this as the drawable resource that is accessed via my call to recyclerView.addItemDecoration(new SimpleDividerItemDecoration(getContext())), no line is drawn at all.
How to solve so a dashed line is shown?
Just add your drawable resource into this item decorator.
DividerItemDecoration decorator = new DividerItemDecoration(ContextCompat.getDrawable(getContext(), R.drawable.line_dashed));
recyclerView.addItemDecoration(decorator);
and DividerItemDecorator class:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mPaddingLeft;
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
mPaddingLeft = 0;
}
public DividerItemDecoration(Drawable divider, int paddingLeft) {
mDivider = divider;
mPaddingLeft = paddingLeft;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null) return;
if (parent.getChildAdapterPosition(view) < 1) return;
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
} else {
outRect.left = mDivider.getIntrinsicWidth();
}
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
final int left = parent.getPaddingLeft() + mPaddingLeft;
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 1; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int size = mDivider.getIntrinsicHeight();
final int top = child.getTop() - params.topMargin;
final int bottom = top + size;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
} else { //horizontal
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 1; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int size = mDivider.getIntrinsicWidth();
final int left = child.getLeft() - params.leftMargin;
final int right = left + size;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else
throw new IllegalStateException("DividerItemDecoration can only be used with a LinearLayoutManager.");
}
}
Should work I tested it.
UPDATE:
android:layerType="software"
add this parameter in xml for recyclerView
Also add size into your shape drawable:
<size android:height="1dp"/>
currently u can use DividerItemDecoration from the box.
recyclerView.apply {
layoutManager = LinearLayoutManager(this#YourFragment.context)
adapter = this#YourFragment.adapter
addItemDecoration(
DividerItemDecoration(
this#YourFragment.context,
DividerItemDecoration.VERTICAL
).apply {
context.getDrawable(R.drawable.divider)?.let {
setDrawable(it)
}
}
)
}
Use the next shape XML:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<size android:height="1dp" />
<solid android:color="#color/primary" />
<stroke
android:width="0.5dp"
android:color="#color/primary"
android:dashWidth="5dp"
android:dashGap="5dp" />
</shape>
Attention: The stroke width must be less than the height of the line. In another case, the line will be not drawn.
Draw dashed line in Android is not so easy deal. Drowable like you showed and even just draw dotted line on canwas (canvas.drawLine(..., paintWithDashEffect)) not always works (not for all devices). You may use android:layerType="software" or draw path. IMHO, the better solution is to not draw dotted line at all (draw just line). But if you really need dotted line, you can use #fearless answer or somethink like this:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private int mDividerSize;
public DividerItemDecoration(int dividerSize) {
mDividerSize = dividerSize;
mPaint = new Paint();
mPaint.setColor(ContextCompat.getColor(context, R.color.colorAccent));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dividerSize);
mPaint.setPathEffect(new DashPathEffect(new float[]{dashGap,dashWidth},0));
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = mDividerSize;
}
#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();
Path path = new Path();
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 + mDividerSize/2;
path.moveTo(left, top);
path.lineTo(right, top);
}
c.drawPath(path, mPaint);
}
}

RecyclerView ItemDecoration: Spacing AND Divider

Below is how I'm doing the spacing for RecyclerView items. It's designed to work with both grids and lists. The spacing works.
What I can't figure out is how to insert a divider line as well. Any help doing so would be greatly appreciated.
SIDE NOTE: if you have a better way to implement the spacing than what I'm currently doing, I'd be very grateful as well :)
public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
private int numOfColumns;
private int listSize;
private int offsetInDp;
private boolean isGridView;
private boolean canScrollHorizontally;
private boolean isBottomRow = false;
public ItemOffsetDecoration(RecyclerView.LayoutManager manager, int listSize, int offsetInDp) {
this(manager, 1, listSize, offsetInDp);
}
public ItemOffsetDecoration(RecyclerView.LayoutManager manager, int numOfColumns, int listSize, int offsetInDp) {
this.numOfColumns = numOfColumns;
this.listSize = listSize;
this.offsetInDp = PixelConversionUtils.dpToPx(offsetInDp);
this.isGridView = manager instanceof GridLayoutManager;
this.canScrollHorizontally = manager.canScrollHorizontally();
}
#Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
// only do left/right spacing if grid or horizontal list
if (isGridView || canScrollHorizontally) {
outRect.left = offsetInDp;
outRect.right = offsetInDp;
}
// only do top/bottom spacing if grid or vertical list
if (isGridView || !canScrollHorizontally) {
int pos = parent.getChildAdapterPosition(view);
boolean isNotTopRow = pos >= numOfColumns;
// Don't add top spacing to top row
if (isNotTopRow) {
outRect.top = offsetInDp;
}
int columnIndex = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
if (pos >= (listSize - numOfColumns) && columnIndex == 0) {
isBottomRow = true;
}
// Don't add bottom spacing to bottom row
if (!isBottomRow && pos < (listSize - numOfColumns)) {
outRect.bottom = offsetInDp;
}
}
}
}
here's a quick visual of what I'm looking to do:
here's what I have:
here's what I want:
You can achieve desired look this way:
first, create a divider Drawable, for this example I've used a simple shape, but you could use default line divider or any other drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="2dp" />
<size android:width="2dp" />
<solid android:color="#000000" />
</shape>
second, in your ItemOffsetDecoration declare Drawable and initialize it:
public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
...
public ItemOffsetDecoration(...) {
mDivider = ContextCompat.getDrawable(context, R.drawable.item_divider);
}
}
third, override onDrawOver() method:
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (isGridView) {
drawVerticalDivider(c, parent);
} else {
drawVerticalDivider(c, parent);
drawHorizontalDivider(c, parent);
}
}
where drawVerticalDivider() & drawHorizontalDivider() are (might be a good idea to refactor them into the single method and control direction of the divider via parameter):
public void drawVerticalDivider(Canvas c, RecyclerView parent) {
if (parent.getChildCount() == 0) return;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getLeft() - params.leftMargin - offsetInDp;
int right = child.getRight() + params.rightMargin + offsetInDp;
int top = child.getBottom() + params.bottomMargin + offsetInDp;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontalDivider(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + offsetInDp;
int right = left + mDivider.getIntrinsicWidth();
int top = child.getTop() - params.topMargin - offsetInDp;
int bottom = child.getBottom() + params.bottomMargin + offsetInDp;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
Result for the Linear and Grid LayoutManagers:
Try placing the following XML snippet to get a divider:
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="72dp"
android:layout_marginRight="16dp"
android:background="123e4152"/>
You can put this in the recyclerView's item layout beneath your items. Also Play around with the margins and background to suit your list.
haha……actualy,i had try like this for Divider ,although with a bit funny : first make you recycleview backgroud with Deep color,and make item_view backgroud white,then marginbottom for every item -> I'm serious, do not vote down :)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_marginBottom="2dp"
android:layout_height="wrap_content">
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>

How do I show dividers in a PreferenceFragment?

I'm using PreferenceFragmentCompat from the v7 support library to display a few settings. My preferences.xml file is as follows -
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:defaultValue="#string/temp_url_default"
android:key="temp_edit_preference_key"
android:title="#string/temp_url_setting_title" />
<EditTextPreference
android:defaultValue=""
android:key="username_preference_key"
android:title="#string/username_setting_title" />
<EditTextPreference
android:defaultValue=""
android:key="password_preference_key"
android:title="#string/password_setting_title" />
<ListPreference
android:key="reset_preference_key"
android:title="#string/reset_setting_title" />
</PreferenceScreen>
Java code -
public class SettingsFragment extends PreferenceFragmentCompat {
#Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.preferences);
}
}
The theme I'm using for preferences is -
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="preferenceTheme">#style/AppTheme.Preference</item>
</style>
<style name="AppTheme.Preference" parent="#style/PreferenceThemeOverlay">
<item name="android:textColorPrimary">#android:color/holo_blue_dark</item>
</style>
The four settings are just displayed one below the other, without any divider in between. How do I show a divider in between two items ?
For a few revision the com.android.support:preference-v7 support library uses the RecyclerView in order to display the settings now. Out of the box the RecyclerView doesn't have a divider but adding an ItemDecoration to the RecyclerView will work:
public class YourPreferenceFragment extends PreferenceFragmentCompat {
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
RecyclerView recyclerView = (RecyclerView) getView().findViewById(R.id.list);
recyclerView.addItemDecoration(
new DividerItemDecorationPreferences(
getActivity(),
getResources().getDimensionPixelSize(R.dimen.row_preferences_margin_horizontal),
getResources().getDimensionPixelSize(R.dimen.row_preferences_margin_horizontal)));
}
}
Obviously you can use whatever Divider you like. As for myself use the following class:
public class DividerItemDecorationPreferences extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int paddingLeft = 0;
private int paddingRight = 0;
public DividerItemDecorationPreferences(Context context, int paddingLeft, int paddingRight) {
mDivider = ContextCompat.getDrawable(context, R.drawable.divider_recycler_view);
this.paddingLeft = paddingLeft;
this.paddingRight = paddingRight;
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = paddingLeft;
int right = parent.getWidth() - paddingRight;
int childCount = parent.getChildCount();
boolean lastIteration = false;
for (int i = 0; i < childCount; i++) {
if (i == childCount - 1)
lastIteration = true;
View child = parent.getChildAt(i);
if (!lastIteration) {
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);
}
}
}
}
divider_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="#1F000000" />
</shape>

How to add dividers and spaces between items in RecyclerView

This is an example of how it could have been done previously in the ListView class, using the divider and dividerHeight parameters:
<ListView
android:id="#+id/activity_home_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#android:color/transparent"
android:dividerHeight="8dp"/>
However, I don't see such possibility in the RecyclerView class.
<android.support.v7.widget.RecyclerView
android:id="#+id/activity_home_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
In that case, is it ok to define margins and/or add a custom divider view directly into a list item's layout or is there a better way to achieve my goal?
October 2016 Update
The version 25.0.0 of Android Support Library introduced the DividerItemDecoration class:
DividerItemDecoration is a RecyclerView.ItemDecoration that can be used as a divider between items of a LinearLayoutManager. It supports both HORIZONTAL and VERTICAL orientations.
Usage:
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
Previous answer
Some answers either use methods that have since become deprecated, or don't give a complete solution, so I tried to do a short, up-to-date wrap-up.
Unlike ListView, the RecyclerView class doesn't have any divider-related parameters. Instead, you need to extend ItemDecoration, a RecyclerView's inner class:
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
All ItemDecorations are drawn in the order they were added, before the item views (in onDraw()) and after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State).
Vertical spacing ItemDecoration
Extend ItemDecoration, add a custom constructor which takes space height as a parameter and override the getItemOffsets() method:
public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int verticalSpaceHeight;
public VerticalSpaceItemDecoration(int verticalSpaceHeight) {
this.verticalSpaceHeight = verticalSpaceHeight;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
outRect.bottom = verticalSpaceHeight;
}
}
If you don't want to insert space below the last item, add the following condition:
if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) {
outRect.bottom = verticalSpaceHeight;
}
Note: you can also modify outRect.top, outRect.left and outRect.right properties for the desired effect.
Divider ItemDecoration
Extend ItemDecoration and override the onDraw() method:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable divider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
divider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
divider = ContextCompat.getDrawable(context, resId);
}
#Override
public void onDraw(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 + divider.getIntrinsicHeight();
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
}
You can either call the first constructor that uses the default Android divider attributes, or the second one that uses your own drawable, for example drawable/divider.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="#ff992900" />
</shape>
Note: if you want the divider to be drawn over your items, override the onDrawOver() method instead.
Usage
To use your new class, add VerticalSpaceItemDecoration or DividerSpaceItemDecoration to RecyclerView, for example in your fragment's onCreateView() method:
private static final int VERTICAL_ITEM_SPACE = 48;
private RecyclerView recyclerView;
private LinearLayoutManager linearLayoutManager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_feed, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_home_recycler_view);
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
//add ItemDecoration
recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));
//or
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
//or
recyclerView.addItemDecoration(
new DividerItemDecoration(getActivity(), R.drawable.divider));
recyclerView.setAdapter(...);
return rootView;
}
There's also Lucas Rocha's library which is supposed to simplify the item decoration process. I haven't tried it though.
Among its features are:
A collection of stock item decorations including:
Item spacing Horizontal/vertical dividers.
List item
Just add
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL));
Also you may need to add the dependency
implementation 'com.android.support:recyclerview-v7:28.0.0'
For customizing it a little bit you can add a custom drawable:
DividerItemDecoration itemDecorator = new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL);
itemDecorator.setDrawable(ContextCompat.getDrawable(getContext(), R.drawable.divider));
You are free to use any custom drawable, for instance:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#color/colorPrimary"/>
<size android:height="0.5dp"/>
</shape>
Might I direct your attention to this particular file on GitHub by Alex Fu:
link
It's the DividerItemDecoration.java example file "pulled straight from the support demos".
I was able to get divider lines nicely after importing this file in my project and add it as an item decoration to the recycler view.
Here's how my onCreateView look like in my fragment containing the Recyclerview:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_recycler_view, container, false);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
return rootView;
}
I'm sure additional styling can be done, but it's a starting point. :)
A simple ItemDecoration implementation for equal spaces between all items:
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
// Add top margin only for the first item to avoid double space between items
if(parent.getChildAdapterPosition(view) == 0) {
outRect.top = space;
}
}
}
The simple one is to set the background color for RecyclerView and a different background color for items. Here is an example...
<android.support.v7.widget.RecyclerView
android:background="#ECEFF1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"/>
And the TextView item (it can be anything though) with bottom margin "x" dp or px.
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="1dp"
android:background="#FFFFFF"/>
The output...
This is simple, and you don't need such complicated code:
DividerItemDecoration divider = new DividerItemDecoration(
mRVMovieReview.getContext(), DividerItemDecoration.VERTICAL
);
divider.setDrawable(
ContextCompat.getDrawable(getBaseContext(), R.drawable.line_divider)
);
mRVMovieReview.addItemDecoration(divider);
Add this in your drawable: line_divider.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="#android:color/black" />
</shape>
The way how I'm handling the Divider view and also Divider Insets is by adding a RecyclerView extension.
1.
Add a new extension file by naming View or RecyclerView:
RecyclerViewExtension.kt
and add the setDivider extension method inside the RecyclerViewExtension.kt file.
/*
* RecyclerViewExtension.kt
* */
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
fun RecyclerView.setDivider(#DrawableRes drawableRes: Int) {
val divider = DividerItemDecoration(
this.context,
DividerItemDecoration.VERTICAL
)
val drawable = ContextCompat.getDrawable(
this.context,
drawableRes
)
drawable?.let {
divider.setDrawable(it)
addItemDecoration(divider)
}
}
2.
Create a Drawable resource file inside of drawable package like recycler_view_divider.xml:
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="10dp"
android:insetRight="10dp">
<shape>
<size android:height="0.5dp" />
<solid android:color="#android:color/darker_gray" />
</shape>
</inset>
where you can specify the left and right margin on android:insetLeft and android:insetRight.
3.
On your Activity or Fragment where the RecyclerView is initialized, you can set the custom drawable by calling:
recyclerView.setDivider(R.drawable.recycler_view_divider)
4.
Cheers 🍺
As I have set ItemAnimators. The ItemDecorator don't enter or exit along with the animation.
I simply ended up in having a view line in my item view layout file of each item. It solved my case. DividerItemDecoration felt to be too much of sorcery for a simple divider.
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#color/lt_gray"/>
I think using a simple divider will help you
To add divider to each item:
1. Add this to drawable directory line_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="#999999" />
</shape>
2. Create SimpleDividerItemDecoration class
I used this example to define this class:
https://gist.github.com/polbins/e37206fbc444207c0e92
package com.example.myapp;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.example.myapp.R;
public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration{
private Drawable mDivider;
public SimpleDividerItemDecoration(Resources resources) {
mDivider = resources.getDrawable(R.drawable.line_divider);
}
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);
}
}
}
3. In activity or fragment that using RecyclerView, inside onCreateView add this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RecyclerView myRecyclerView = (RecyclerView) layout.findViewById(R.id.my_recycler_view);
myRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getResources()));
....
}
4. To add spacing between Items
You just need to add padding property to your item view
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:padding="4dp"
>
..... item structure
</RelativeLayout>
If anyone is looking to only add, say, 10 dp spacing between items, you can do so by setting a drawable to DividerItemDecoration:
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
recyclerView.getContext(),
layoutManager.getOrientation()
);
dividerItemDecoration.setDrawable(
ContextCompat.getDrawable(getContext(), R.drawable.divider_10dp)
);
recyclerView.addItemDecoration(dividerItemDecoration);
Where divider_10dpis a drawable resource containing:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:height="10dp"/>
<solid android:color="#android:color/transparent"/>
</shape>
Since there is no right way to implement this yet properly using Material Design, I just did the following trick to add a divider on the list item directly:
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/dividerColor"/>
Instead of creating a shape xml for changing the divider height and color, you can create it programmatically like:
val divider = DividerItemDecoration(
context,
DividerItemDecoration.VERTICAL)
divider.setDrawable(ShapeDrawable().apply {
intrinsicHeight = resources.getDimensionPixelOffset(R.dimen.dp_15)
paint.color = Color.RED // Note:
// Currently (support version 28.0.0), we
// can not use tranparent color here. If
// we use transparent, we still see a
// small divider line. So if we want
// to display transparent space, we
// can set color = background color
// or we can create a custom ItemDecoration
// instead of DividerItemDecoration.
})
recycler_devices.addItemDecoration(divider)
OCTOBER 2016 UPDATE
With support library v25.0.0 there finally is a default implementation of basic horizontal and vertical dividers available!
DividerItemDecoration
Add a margin to your view. It worked for me.
android:layout_marginTop="10dp"
If you just want to add equal spacing and want to do it in XML, just set padding to your RecyclerView and equal amount of layoutMargin to the item you inflate into your RecyclerView, and let the background color determine the spacing color.
For those who are looking just for spaces between items in the RecyclerView, see my approach where you get equal spaces between all items, except in the first and last items where I gave a bigger padding. I only apply padding to left/right in a horizontal LayoutManager and to top/bottom in a vertical LayoutManager.
public class PaddingItemDecoration extends RecyclerView.ItemDecoration {
private int mPaddingPx;
private int mPaddingEdgesPx;
public PaddingItemDecoration(Activity activity) {
final Resources resources = activity.getResources();
mPaddingPx = (int) resources.getDimension(R.dimen.paddingItemDecorationDefault);
mPaddingEdgesPx = (int) resources.getDimension(R.dimen.paddingItemDecorationEdge);
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
final int itemPosition = parent.getChildAdapterPosition(view);
if (itemPosition == RecyclerView.NO_POSITION) {
return;
}
int orientation = getOrientation(parent);
final int itemCount = state.getItemCount();
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
/** Horizontal */
if (orientation == LinearLayoutManager.HORIZONTAL) {
/** All positions */
left = mPaddingPx;
right = mPaddingPx;
/** First position */
if (itemPosition == 0) {
left += mPaddingEdgesPx;
}
/** Last position */
else if (itemCount > 0 && itemPosition == itemCount - 1) {
right += mPaddingEdgesPx;
}
}
/** Vertical */
else {
/** All positions */
top = mPaddingPx;
bottom = mPaddingPx;
/** First position */
if (itemPosition == 0) {
top += mPaddingEdgesPx;
}
/** Last position */
else if (itemCount > 0 && itemPosition == itemCount - 1) {
bottom += mPaddingEdgesPx;
}
}
if (!isReverseLayout(parent)) {
outRect.set(left, top, right, bottom);
} else {
outRect.set(right, bottom, left, top);
}
}
private boolean isReverseLayout(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getReverseLayout();
} else {
throw new IllegalStateException("PaddingItemDecoration can only be used with a LinearLayoutManager.");
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else {
throw new IllegalStateException("PaddingItemDecoration can only be used with a LinearLayoutManager.");
}
}
}
File dimens.xml
<resources>
<dimen name="paddingItemDecorationDefault">10dp</dimen>
<dimen name="paddingItemDecorationEdge">20dp</dimen>
</resources>
Here is a simple hack to add a divider
Just add a background to the layout of your recycler item as follows
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/shape_border"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="#+id/imageViewContactLogo"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginRight="10dp"
android:src="#drawable/ic_user" />
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.92"
android:gravity="center|start"
android:orientation="vertical">
<TextView
android:id="#+id/textViewContactName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/textViewStatusOrNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:singleLine="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
<TextView
android:id="#+id/textViewUnreadCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:padding="5dp"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#color/red"
android:textSize="22sp" />
<Button
android:id="#+id/buttonInvite"
android:layout_width="54dp"
android:layout_height="wrap_content"
android:background="#drawable/ic_add_friend" />
</LinearLayout>
Create the following shape_border.xml file in the drawable folder:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="270"
android:centerColor="#android:color/transparent"
android:centerX="0.01"
android:startColor="#000" />
</shape>
Here is the final result - a RecyclerView with divider.
This doesn't actually solve the problem, but as a temporary workaround, you can set the useCompatPadding property on the card in your XML layout to make it measure the same as it does on pre-Lollipop versions.
card_view:cardUseCompatPadding="true"
I forked the DividerItemDecoration from an older gist and simplified it to fit my use case, and I also modified it to draw the dividers the way they are drawn in ListView, including a divider after the last list item. This will also handle vertical ItemAnimator animations:
1) Add this class to your project:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable divider;
public DividerItemDecoration(Context context) {
try {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
divider = a.getDrawable(0);
a.recycle();
} catch (Resources.NotFoundException e) {
// TODO Log or handle as necessary.
}
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (divider == null) return;
if (parent.getChildAdapterPosition(view) < 1) return;
if (getOrientation(parent) == LinearLayoutManager.VERTICAL)
outRect.top = divider.getIntrinsicHeight();
else
throw new IllegalArgumentException("Only usable with vertical lists");
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (divider == null) {
super.onDrawOver(c, parent, state);
return;
}
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int size = divider.getIntrinsicHeight();
final int top = (int) (child.getTop() - params.topMargin - size + child.getTranslationY());
final int bottom = top + size;
divider.setBounds(left, top, right, bottom);
divider.draw(c);
if (i == childCount - 1) {
final int newTop = (int) (child.getBottom() + params.bottomMargin + child.getTranslationY());
final int newBottom = newTop + size;
divider.setBounds(left, newTop, right, newBottom);
divider.draw(c);
}
}
}
private int getOrientation(RecyclerView parent) {
if (!(parent.getLayoutManager() instanceof LinearLayoutManager))
throw new IllegalStateException("Layout manager must be an instance of LinearLayoutManager");
return ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
}
}
2) Add the decorator to your RecylerView:
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
I feel like there's a need for a simple, code-based answer that doesn't use XML
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
ShapeDrawable shapeDrawableForDivider = new ShapeDrawable(new RectShape());
int dividerThickness = // (int) (SomeOtherView.getHeight() * desiredPercent);
shapeDrawableForDivider.setIntrinsicHeight(dividerThickness);
shapeDrawableForDivider.setAlpha(0);
dividerItemDecoration.setDrawable(shapeDrawableForDivider);
recyclerView.addItemDecoration(dividerItemDecoration);
I love this answer so much, I re-wrote it in a single-expression Kotlin answer:
recyclerView.addItemDecoration(DividerItemDecoration(this,DividerItemDecoration.VERTICAL).also { deco ->
with (ShapeDrawable(RectShape())){
intrinsicHeight = (resources.displayMetrics.density * 24).toInt()
alpha = 0
deco.setDrawable(this)
}
})
This does the same thing as #Nerdy's original answer, except it sets the height of the divider to 24dp instead of a percentage of another view's height.
Here's a decoration that lets you set a spacing between items as well as a spacing on the edges. This works for both HORIZONTAL and VERTICAL layouts.
class LinearSpacingDecoration(
#Px private val itemSpacing: Int,
#Px private val edgeSpacing: Int = 0
): RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val count = parent.adapter?.itemCount ?: 0
val position = parent.getChildAdapterPosition(view)
val leading = if (position == 0) edgeSpacing else itemSpacing
val trailing = if (position == count - 1) edgeSpacing else 0
outRect.run {
if ((parent.layoutManager as? LinearLayoutManager)?.orientation == LinearLayout.VERTICAL) {
top = leading
bottom = trailing
} else {
left = leading
right = trailing
}
}
}
}
Usage:
recyclerView.addItemDecoration(LinearSpacingDecoration(itemSpacing = 10, edgeSpacing = 20))
Taken from a Google search, add this ItemDecoration to your RecyclerView:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
public DividerItemDecoration(Context context, AttributeSet attrs) {
final TypedArray a = context
.obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider});
mDivider = a.getDrawable(0);
a.recycle();
}
public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
boolean showLastDivider) {
this(context, attrs);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
boolean showLastDivider) {
this(divider);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null) {
return;
}
if (parent.getChildPosition(view) < 1) {
return;
}
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
} else {
outRect.left = mDivider.getIntrinsicWidth();
}
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
// Initialization needed to avoid compiler warning
int left = 0, right = 0, top = 0, bottom = 0, size;
int orientation = getOrientation(parent);
int childCount = parent.getChildCount();
if (orientation == LinearLayoutManager.VERTICAL) {
size = mDivider.getIntrinsicHeight();
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
} else { // Horizontal
size = mDivider.getIntrinsicWidth();
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
}
for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getTop() - params.topMargin;
bottom = top + size;
} else { // Horizontal
left = child.getLeft() - params.leftMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
// Show the last divider
if (mShowLastDivider && childCount > 0) {
View child = parent.getChildAt(childCount - 1);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getBottom() + params.bottomMargin;
bottom = top + size;
} else { // hHorizontal
left = child.getRight() + params.rightMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else {
throw new IllegalStateException(
"DividerItemDecoration can only be used with a LinearLayoutManager.");
}
}
}
This link worked like a charm for me:
https://gist.github.com/lapastillaroja/858caf1a82791b6c1a36
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.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
public DividerItemDecoration(Context context, AttributeSet attrs) {
final TypedArray a = context
.obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider});
mDivider = a.getDrawable(0);
a.recycle();
}
public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
boolean showLastDivider) {
this(context, attrs);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
boolean showLastDivider) {
this(divider);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null) {
return;
}
if (parent.getChildPosition(view) < 1) {
return;
}
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
} else {
outRect.left = mDivider.getIntrinsicWidth();
}
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
// Initialization needed to avoid compiler warning
int left = 0, right = 0, top = 0, bottom = 0, size;
int orientation = getOrientation(parent);
int childCount = parent.getChildCount();
if (orientation == LinearLayoutManager.VERTICAL) {
size = mDivider.getIntrinsicHeight();
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
} else { //horizontal
size = mDivider.getIntrinsicWidth();
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
}
for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getTop() - params.topMargin;
bottom = top + size;
} else { //horizontal
left = child.getLeft() - params.leftMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
// show last divider
if (mShowLastDivider && childCount > 0) {
View child = parent.getChildAt(childCount - 1);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getBottom() + params.bottomMargin;
bottom = top + size;
} else { // horizontal
left = child.getRight() + params.rightMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else {
throw new IllegalStateException(
"DividerItemDecoration can only be used with a LinearLayoutManager.");
}
}
}
Then in your activity:
mCategoryRecyclerView.addItemDecoration(
new DividerItemDecoration(this, null));
Or this if you are using a fragment:
mCategoryRecyclerView.addItemDecoration(
new DividerItemDecoration(getActivity(), null));
We can decorate the items using various decorators attached to the recyclerview such as the DividerItemDecoration:
Simply use the following ...taken from the answer byEyesClear:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
mDivider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
mDivider = ContextCompat.getDrawable(context, resId);
}
#Override
public void onDraw(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);
}
}
}
And then use the above as follows:
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);
This will display dividers between each item within the list as shown below:
And for those of who are looking for more details can check out this guide Using the RecyclerView _ CodePath Android Cliffnotes.
Some answers here suggest the use of margins, but the catch is that:
If you add both top and bottom margins, they will appear both added between items and they will be too large. If you only add either, there will be no margin either at the top or the bottom of the whole list. If you add half of the distance at the top, half at the bottom, the outer margins will be too small.
Thus, the only aesthetically correct solution is the divider that the system knows where to apply properly: between items, but not above or below items.
For GridLayoutManager I use this:
public class GridSpacesItemDecoration : RecyclerView.ItemDecoration
{
private int space;
public GridSpacesItemDecoration(int space) {
this.space = space;
}
public override void GetItemOffsets(Android.Graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
{
var position = parent.GetChildLayoutPosition(view);
/// Only for GridLayoutManager Layouts
var manager = parent.GetLayoutManager() as GridLayoutManager;
if (parent.GetChildLayoutPosition(view) < manager.SpanCount)
outRect.Top = space;
if (position % 2 != 0) {
outRect.Right = space;
}
outRect.Left = space;
outRect.Bottom = space;
}
}
This works for any span count you have.
You can easily add it programmatically.
If your Layout Manager is Linearlayout then you can use:
DividerItemDecoration is a RecyclerView.ItemDecoration that can be
used as a divider between items of a LinearLayoutManager. It supports
both HORIZONTAL and VERTICAL orientations.
mDividerItemDecoration =
new DividerItemDecoration(recyclerView.getContext(),
mLayoutManager.getOrientation());
recyclerView.addItemDecoration(mDividerItemDecoration);
Source
If you want to add the same space for items, the simplest way is to add top+left padding for RecycleView and right+bottom margins to card items.
File dimens.xml
<resources>
<dimen name="divider">1dp</dimen>
</resources>
File list_item.xml
<CardView
android:layout_marginBottom="#dimen/divider"
android:layout_marginRight="#dimen/divider">
...
</CardView>
File list.xml
<RecyclerView
...
android:paddingLeft="#dimen/divider"
android:paddingTop="#dimen/divider" />
In order to accomplish spacing between items in a RecylerView, we can use ItemDecorators:
addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.getChildAdapterPosition(view) > 0) {
outRect.top = 8.dp // Change this value with anything you want. Remember that you need to convert integers to pixels if you are working with dps :)
}
}
})
A few things to have in consideration given the code I pasted:
You don't really need to call super.getItemOffsets but I chose to, because I want to extend the behavior defined by the base class. If the library got an update doing more logic behind the scenes, we would miss it.
As an alternative to adding top spacing to the Rect, you could also add bottom spacing, but the logic related to getting the last item of the adapter is more complex, so this might be slightly better.
I used an extension property to convert a simple integer to dps: 8.dp. Something like this might work:
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt()
// Extension function works too, but invoking it would become something like 8.dp()
I have added a line in a list item like below:
<View
android:id="#+id/divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#color/dividerColor"/>
"1px" will draw the thin line.
If you want to hide the divider for the last row, then use divider.setVisiblity(View.GONE); on the onBindViewHolder for the last list Item.
One of the ways is by using the cardview and recycler view together. We can easily add an effect, like a divider.
Example: Create dynamic lists with RecyclerView
And another is by adding a view as a divider to a list_item_layout of a recycler view.
<View
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/colorAccent" />
public class CommonItemSpaceDecoration extends RecyclerView.ItemDecoration {
private int mSpace = 0;
private boolean mVerticalOrientation = true;
public CommonItemSpaceDecoration(int space) {
this.mSpace = space;
}
public CommonItemSpaceDecoration(int space, boolean verticalOrientation) {
this.mSpace = space;
this.mVerticalOrientation = verticalOrientation;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.top = SizeUtils.dp2px(view.getContext(), mSpace);
if (mVerticalOrientation) {
if (parent.getChildAdapterPosition(view) == 0) {
outRect.set(0, SizeUtils.dp2px(view.getContext(), mSpace), 0, SizeUtils.dp2px(view.getContext(), mSpace));
} else {
outRect.set(0, 0, 0, SizeUtils.dp2px(view.getContext(), mSpace));
}
} else {
if (parent.getChildAdapterPosition(view) == 0) {
outRect.set(SizeUtils.dp2px(view.getContext(), mSpace), 0, 0, 0);
} else {
outRect.set(SizeUtils.dp2px(view.getContext(), mSpace), 0, SizeUtils.dp2px(view.getContext(), mSpace), 0);
}
}
}
}
This will add space in every item's top and bottom (or left and right). Then you can set it to your recyclerView.
recyclerView.addItemDecoration(new CommonItemSpaceDecoration(16));
File SizeUtils.java
public class SizeUtils {
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}

Categories

Resources