Related
I am trying to put equal space between the items of RecyclerView. For that I am using the below SpanningLinearLayoutManager. What it does is, it will auto distribute space between the items in a RecyclerView but it will make the RecyclerView un-scrollable. All the items will come inside the width of the parent. This works fine for 5 or 6 items. But if the list has around 10 elements all will get really close to each other. I want to distribute the space between items equally and make the recyclerview scrollable way.
I have a child RecyclerView inside a parent RecyclerView. I want to equally distribute space between the items of the child RecyclerView
Here is my code:
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class SpanningLinearLayoutManager extends LinearLayoutManager {
public SpanningLinearLayoutManager(Context context) {
super(context);
}
public SpanningLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public SpanningLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return spanLayoutSize(super.generateDefaultLayoutParams());
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return spanLayoutSize(super.generateLayoutParams(c, attrs));
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
return spanLayoutSize(super.generateLayoutParams(lp));
}
#Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
return super.checkLayoutParams(lp);
}
private RecyclerView.LayoutParams spanLayoutSize(RecyclerView.LayoutParams layoutParams){
if(getOrientation() == HORIZONTAL){
layoutParams.width = (int) Math.round(getHorizontalSpace() / (double) getItemCount());
}
else if(getOrientation() == VERTICAL){
layoutParams.height = (int) Math.round(getVerticalSpace() / (double) getItemCount());
}
return layoutParams;
}
#Override
public boolean canScrollVertically() {
return false;
}
#Override
public boolean canScrollHorizontally() {
return false;
}
private int getHorizontalSpace() {
return getWidth() - getPaddingRight() - getPaddingLeft();
}
private int getVerticalSpace() {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
}
Declare these two variables flagDecoration and mItemDecoration:
private var flagDecoration = false
private var mItemDecoration: RecyclerView.ItemDecoration = object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.left = 20 //use the value as per your need
outRect.right = 20
outRect.top = 20
outRect.bottom = 20
flagDecoration = true
}
}
Add use it in your recyclerView.
if (!flagDecoration) {
recyclerView.addItemDecoration(mItemDecoration)
}
You do not need a custom LayoutManager to achieve this. You can the desired result by using LinearLayoutManager.
All you need to add spacing to every child item is an ItemDecorator.
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration?hl=en
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.top = spacing;
}
..
recyclerView.layoutManager = LinearLayoutManager(..)
recyclerView.addItemDecoration(MyItemDecorator())
I want screen to look like following, with the help of gridview. I am able to add 1 column and two column using setSpanSizeLookup
But how to add two columns with dynamic width?
final GridLayoutManager gridLayoutManager=new GridLayoutManager(ExploreMenuActivity.this,2);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(position == 0)
{
return 2;
}else {
return 1;
}
}
});
Update :
I have used Flexbox layout by google .
implementation 'com.google.android:flexbox:0.3.1'
My Main activity
FlexboxLayoutManager flexboxLayoutManager=new FlexboxLayoutManager(ExploreMenuActivity.this);
flexboxLayoutManager.setFlexWrap(FlexWrap.WRAP);
flexboxLayoutManager.setAlignItems(AlignItems.STRETCH);
recycler_ExploreProduct.setLayoutManager(flexboxLayoutManager);
My .xml file
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/img"
android:layout_width="#dimen/_140sdp"
android:background="#color/color_white_gray"
android:layout_height="#dimen/_140sdp"
android:scaleType="fitCenter"
android:src="#drawable/p_7" />
and My adapter
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), aryImages[position], null);
holder.bindTo(drawable, position);
}
class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
public ViewHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.img);
}
void bindTo(Drawable drawable, int position) {
imageView.setImageDrawable(drawable);
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int screenWidth = displayMetrics.widthPixels;
Log.d(TAG,"## bindTo before if");
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
Log.d(TAG,"## bindTo inside if");
FlexboxLayoutManager.LayoutParams flexboxLp = (FlexboxLayoutManager.LayoutParams) lp;
if (position == 0) {
flexboxLp.width = screenWidth / 3;
} else if (position == 1) {
flexboxLp.width = screenWidth / 2;
} else if (position == 2) {
flexboxLp.width = screenWidth / 2;
} else if (position == 3) {
flexboxLp.width = screenWidth / 3;
} else if (position == 4) {
flexboxLp.width = screenWidth / 3;
} else if (position == 5) {
flexboxLp.width = screenWidth / 2;
} else if (position == 6) {
flexboxLp.width = screenWidth;
} else if (position == 7) {
flexboxLp.width = screenWidth / 3;
} else if (position == 8) {
flexboxLp.width = screenWidth / 2;
} else if (position == 9) {
flexboxLp.width = screenWidth;
} else if (position == 10) {
flexboxLp.width = screenWidth / 2;
} else if (position == 11) {
flexboxLp.width = screenWidth / 3;
} else if (position == 12) {
flexboxLp.width = screenWidth;
} else if (position == 13) {
flexboxLp.width = screenWidth / 3;
} else if (position == 14) {
flexboxLp.width = screenWidth / 2;
} else if (position == 15) {
flexboxLp.width = screenWidth / 2;
} else if (position == 16) {
flexboxLp.width = screenWidth / 3;
} else if (position == 17) {
flexboxLp.width = screenWidth;
} else if (position == 18) {
flexboxLp.width = screenWidth / 3;
} else if (position == 19) {
flexboxLp.width = screenWidth / 2;
}
}
}
Right now I am able to achieve output using above static conditions,
How can I do this things dynamic, when my data will come from API.
You need to use stagard LayoutManager in Recyclerview to get the required results.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity"
android:background="#ffffff"
>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
>
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
MainActivity.java
package com.cfsuman.me.androidcode;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.Window;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private Context mContext;
RelativeLayout mRelativeLayout;
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Request window feature action bar
requestWindowFeature(Window.FEATURE_ACTION_BAR);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the application context
mContext = getApplicationContext();
// Change the action bar color
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(Color.RED));
// Get the widgets reference from XML layout
mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
// Initialize a new String array
String[] colors = {
"Red","Green","Blue","Yellow","Magenta","Cyan","Orange",
"Aqua","Azure","Beige","Bisque","Brown","Coral","Crimson"
};
/*
StaggeredGridLayoutManager
A LayoutManager that lays out children in a staggered grid formation. It supports
horizontal & vertical layout as well as an ability to layout children in reverse.
Staggered grids are likely to have gaps at the edges of the layout. To avoid these
gaps, StaggeredGridLayoutManager can offset spans independently or move items
between spans. You can control this behavior via setGapStrategy(int).
*/
/*
public StaggeredGridLayoutManager (int spanCount, int orientation)
Creates a StaggeredGridLayoutManager with given parameters.
Parameters
spanCount : If orientation is vertical, spanCount is number of columns.
If orientation is horizontal, spanCount is number of rows.
orientation : VERTICAL or HORIZONTAL
*/
// Define a layout for RecyclerView
mLayoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
// Initialize a new instance of RecyclerView Adapter instance
mAdapter = new ColorAdapter(mContext,colors);
// Set the adapter for RecyclerView
mRecyclerView.setAdapter(mAdapter);
}
}
ColorAdapter.java
package com.cfsuman.me.androidcode;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Random;
public class ColorAdapter extends RecyclerView.Adapter<ColorAdapter.ViewHolder>{
private String[] mDataSet;
private Context mContext;
private Random mRandom = new Random();
public ColorAdapter(Context context,String[] DataSet){
mDataSet = DataSet;
mContext = context;
}
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView mTextView;
public ViewHolder(View v){
super(v);
mTextView = (TextView)v.findViewById(R.id.tv);
}
}
#Override
public ColorAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
// Create a new View
View v = LayoutInflater.from(mContext).inflate(R.layout.custom_view,parent,false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position){
holder.mTextView.setText(mDataSet[position]);
// Set a random height for TextView
holder.mTextView.getLayoutParams().height = getRandomIntInRange(250,75);
// Set a random color for TextView background
holder.mTextView.setBackgroundColor(getRandomHSVColor());
}
#Override
public int getItemCount(){
return mDataSet.length;
}
// Custom method to get a random number between a range
protected int getRandomIntInRange(int max, int min){
return mRandom.nextInt((max-min)+min)+min;
}
// Custom method to generate random HSV color
protected int getRandomHSVColor(){
// Generate a random hue value between 0 to 360
int hue = mRandom.nextInt(361);
// We make the color depth full
float saturation = 1.0f;
// We make a full bright color
float value = 1.0f;
// We avoid color transparency
int alpha = 255;
// Finally, generate the color
int color = Color.HSVToColor(alpha, new float[]{hue, saturation, value});
// Return the color
return color;
}
}
build.gradle [dependencies]
compile 'com.android.support:recyclerview-v7:23.0.1'
compile 'com.android.support:cardview-v7:23.0.1'
use this https://github.com/felipecsl/AsymmetricGridView
http://myhexaville.com/2017/02/27/android-flexboxlayout/
I am sure this will help you.
You need to use flexbox layout
`<com.google.android.flexbox.FlexboxLayout
android:id="#+id/flex_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:justifyContent="flex_start"
app:alignContent="flex_start"
app:alignItems="flex_start"
app:flexDirection="row"
app:flexWrap="wrap">
</com.google.android.flexbox.FlexboxLayout>`
and then add view dynamically like
`TextView rowTextView = (TextView)view.findViewById(R.id.flex_tv);
rowTextView.setTag(items.get(i));
rowTextView.setId(i);
rowTextView.setText(items.get(i));
flexboxLayout.addView(view);`
Please add compile 'com.google.android:flexbox:0.3.0#aar' in your gradle
Use LinearLayout with equal weight for both ImageViews Something like below xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/image1"
android:layout_width="0dp"
android:src="#mipmap/ic_launcher"
android:layout_weight="1"
android:background="#ff0000"
android:layout_height="wrap_content"/>
<ImageView
android:id="#+id/image2"
android:layout_width="0dp"
android:layout_weight="1"
android:background="#ffff00"
android:src="#mipmap/ic_launcher"
android:layout_height="wrap_content"/>
</LinearLayout>
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?
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);
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.
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)
}
}
}
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
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()
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
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="#color/DividerColor" />
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)
}
}
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()
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)
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.
While many of the answers here have been helping enough, until the Google's MaterialDividerItemDecoration library come to help. With this library you don't even need to implement a custom class to control properties like insets and drawing for the last item etc.
There you have a quick show case:
// layoutManager.getOrientation() can be used alternatively for the orientation parameter
MaterialDividerItemDecoration mdid =
new MaterialDividerItemDecoration(requireContext(), MaterialDividerItemDecoration.VERTICAL);
// Note that the inset value must be in pixels here let's say we want to inset 100px
mdid.setDividerInsetStart(100);
// And we don't want the item decorator to draw a divider for the last item in the list.
mdid.setLastItemDecorated(false);
recyclerView.addItemDecoration(mdid); // DONE
The cleanest way is just to copy the original DividerItemDecoration class and put it inside androidx.recyclerview.widget package inside you app's java source. Then just change these lines inside the class (there are 2 of them - one for vertical and one for horizontal:
final int childCount = parent.getChildCount();
to
final int childCount = parent.getChildCount() -1;
No other code changes needed. Doing so will also retain all other properties and behaviors of DividerItemDecoration class.
Here's the modified class if you need it:
/*
* Copyright 2018 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 androidx.recyclerview.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.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* 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();
/**
* 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}.
*/
public DividerItemDecoration(Context context, int orientation) {
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);
}
/**
* 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;
}
/**
* #return the {#link Drawable} for this divider.
*/
#Nullable
public Drawable getDrawable() {
return mDivider;
}
#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;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
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() -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());
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;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
if (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();
}
final int 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());
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.set(0, 0, 0, 0);
return;
}
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
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()
}
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>
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
)
)
}
}
}
The answer of Bhuvanesh BS is working but in my case spaces between items were ignored and divider showed to the top.
So, I want to share a (Kotlin) solution with space support.
class DividerDecoratorWithoutLastLine(
private val dividerDrawable: Drawable,
private val marginVerticalPx: Int = 0,
private val marginHorizontalPx: Int = 0
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
with(outRect) {
right = marginHorizontalPx
left = marginHorizontalPx
top = marginVerticalPx
bottom = marginVerticalPx
}
}
override fun onDrawOver(c: 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 + marginVerticalPx
val dividerBottom = dividerTop + dividerDrawable.intrinsicHeight
dividerDrawable.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom)
dividerDrawable.draw(c)
}
}
}
I am using cardview inside a RecyclerView and to make the RecyclerView scroll horizontal i have initialized the view with a layout manager like below:
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
At the moment the items in the view are scrolling endlessly/smooth. I would like it to stop when one item is shown on screen, almost like a snappy effect. Can this be achieved?
Thanks in advance.
I used this class:
SnappyRecyclerView
package icn.premierandroid.misc;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class SnappyRecyclerView extends RecyclerView {
// Use it with a horizontal LinearLayoutManager
// Based on http://stackoverflow.com/a/29171652/4034572
public SnappyRecyclerView(Context context) {
super(context);
}
public SnappyRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public SnappyRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean fling(int velocityX, int velocityY) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
// views on the screen
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
if (Math.abs(velocityX) < 1000) {
// The fling is slow -> stay at the current page if we are less than half through,
// or go to the next page if more than half through
if (leftEdge > screenWidth / 2) {
// go to next page
smoothScrollBy(-scrollDistanceRight, 0);
} else if (rightEdge < screenWidth / 2) {
// go to next page
smoothScrollBy(scrollDistanceLeft, 0);
} else {
// stay at current page
if (velocityX > 0) {
smoothScrollBy(-scrollDistanceRight, 0);
} else {
smoothScrollBy(scrollDistanceLeft, 0);
}
}
return true;
} else {
// The fling is fast -> go to next page
if (velocityX > 0) {
smoothScrollBy(scrollDistanceLeft, 0);
} else {
smoothScrollBy(-scrollDistanceRight, 0);
}
return true;
}
}
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
// If you tap on the phone while the RecyclerView is scrolling it will stop in the middle.
// This code fixes this. This code is not strictly necessary but it improves the behaviour.
if (state == SCROLL_STATE_IDLE) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
// views on the screen
int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);
// distance we need to scroll
int leftMargin = (screenWidth - lastView.getWidth()) / 2;
int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
int leftEdge = lastView.getLeft();
int rightEdge = firstView.getRight();
int scrollDistanceLeft = leftEdge - leftMargin;
int scrollDistanceRight = rightMargin - rightEdge;
if (leftEdge > screenWidth / 2) {
smoothScrollBy(-scrollDistanceRight, 0);
} else if (rightEdge < screenWidth / 2) {
smoothScrollBy(scrollDistanceLeft, 0);
}
}
}
}
in XML (put your package route to the class e.g. mine is icn.premierandroid.misc.SnappyRecyclerView:
<packagename.SnappyRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/recycler_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_weight="0.34" />
You shouldn't need to change anything if you have a RecyclerView initialized in your class already.
Like so:
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
// LinearLayoutManager is used here, this will layout the elements in a similar fashion
// to the way ListView would layout elements. The RecyclerView.LayoutManager defines how
// elements are laid out.
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(mLayoutManager);
This should satisfy full width of screen elements only, as you've asked for.
What you are looking for, is the snapping effect.
I've not personally used this class but I believe this would work for you.
https://gist.github.com/lauw/fc84f7d04f8c54e56d56
What this does is, extend the current Android's recyclerview and adds snapping functionality to it.
Add this class to your project and replace the recyclerview with your current recyclerview.
You can enabled snapping of your items to the screen using the setSnapEnabled() method.
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);
}
}