setting a custom margin between view of a recyclerview - android

I am trying to make recyclerview in which I can put a custom margin on view item based on an id in android
I tried custome adapter with a different view which we can put in one recycler view but I need only one view and only want to do is put custom margin between view based on id.
if (category_detail.getId()==0){
ViewGroup.MarginLayoutParams marginLayoutParams =
(ViewGroup.MarginLayoutParams) rec_discount.getLayoutParams();
marginLayoutParams.setMargins(0, 500, 0, 0);
rec_discount.setLayoutParams(marginLayoutParams);
}

You can use ItemDecoration to achieve this.
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;
}
}
}
This will add a margin of "space" pixels on the left, right and bottom sides of all views in the recycler view. You can customize as per your requirements.
To use this class you can do this:
recyclerView.addItemDecoration(new SpacesItemDecoration(10));

Related

Reset Recycler view decoration with diff utils

I have a grid recycler view that I update using diff utils, but if something in the list data causes an item to go from any row below the first row, up to the first row then the decoration lacks padding top, this is kind of expected as I'm only adding padding to the top of an item in the list if it is in the first row. I then add bottom padding to every item,
Recycler View Decoration
public class GridSpacing extends RecyclerView.ItemDecoration {
private final int spacing;
private final int numColumns;
public GridSpacing(int spacing, int numColumns) {
this.spacing = spacing;
this.numColumns = numColumns;
}
#Override
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int position = parent.getChildLayoutPosition(view);
if (position < numColumns){
outRect.top = spacing;
}
if (position % 2 != 0) {
outRect.right = spacing;
}
outRect.left = spacing;
outRect.bottom = spacing;
}
}
If I call notifyDatasetChanged, this will recalculate my decoration and add the padding to the top row, but diff utils is not calling notifyDatasetChanged (think it calls a combination of item range changed, item inserted, item removed, to keep animations) so my decoration doesn't get reapplied, I could probably reapply it myself but that seems like a hack to have to remove and reapply the decoration every time an item changes, does anyone know of a better approach?
so instead of trying to reapply the decorator I'm going to remove the padding top in the decorator and instead add padding to the top and clip to padding false on the recycler view
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:clipToPadding="false"
android:id="#+id/recyclerView"/>

Divide width between two columns in GridLayoutManager

I used GridLayoutManager as my layout manger for recycler view.
mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
the result is like below:
but I want to divide width between two columns equally. how can I do that?
Try adding this on your recyclerView:
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int spanCount = 2;
int spacing = 10;//spacing between views in grid
if (position >= 0) {
int column = position % spanCount; // item column
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacing;
}
outRect.bottom = spacing; // item bottom
} else {
outRect.left = 0;
outRect.right = 0;
outRect.top = 0;
outRect.bottom = 0;
}
}
});
Setting the column count like you do:
mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
... actually makes the grid splitting into columns of the same width.
This suggests that the problem lies somewhere else, probably in the item layout. I suppose that some whitespaces (paddings/margins) are responsible for the fact that the items are shifted to the right. Try to remove these spacings and check if it works.
If it helps, you can always add new spacing with ItemDecoration but it isn't the reason for the incorrect size of your columns.
I think that turning on Show Layout Bounds option may be helpful for you.
I wrote a library for equal spacing which supports Vertical/Horizontal, LTR/RTL, LinearLayout/GridLayout manager and Edge inclusion. Its basically a single file, so you can copy paste that file into your code.
I tried to support StaggeredGridLayout but span index returned by this layout is not reliable. I would be glad to hear any suggestion for that.
You can use custom ItemDecoration and you can split the space as you want :
SpacesItemDecoration can help you with this:
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.bottom = space;
// Add top margin only for the first item to avoid double space between items
if (parent.getChildLayoutPosition(view)%2 == 0) {
outRect.left = space;
outRect.right = space;
} else {
outRect.right = space;
}
}
}
In your acitivty or fragment :
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
spacesItemDecoration = new SpacesItemDecoration(spacingInPixels);
recyclerview.addItemDecoration(spacesItemDecoration);
You can change the outRect.left & outRect.Right as per your need
You can use ItemDecoration for this purpose,have a look at here
And there are some very good answers here also

Adding ItemDecoration to Recycler View Causes Headers to Shrink

I have a RecyclerView that is using a StaggeredGridLayoutManager to display items that are sectioned by date. For each section(day) I have a header view that is inserted into the recycler view and in my OnBindViewHolderAction method I set the headers to be FullSpan. My issue is, when I add my ItemDecoration to the RecyclerView to give all the items spacing, the header views get covered by the spacing (as if padding were added without the view size increasing) instead of margin spacing being added. I am not sure where to look next, any suggestions would be helpful. Thanks.
Without spacing item decoration:
With spacing item decoration:
What is should look like:
This last picture is the same RecyclerView and ItemDecoration but on a GridLayoutManager instead of a StaggeredGridLayoutManager.
public void OnBindViewHolderAction(RecyclerView.ViewHolder holder, int position)
{
var item = _collectionAdapter.SectionedList[position];
if (holder is ActivityFeedItemViewHolder) {
var viewHolder = (ActivityFeedItemViewHolder)holder;
viewHolder.Bind((ActivityFeedItemViewModel)item);
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams)holder.ItemView.LayoutParameters;
layoutParams.FullSpan = false;
}
if (holder is RecyclerHeaderViewHolder) {
var viewHolder = (RecyclerHeaderViewHolder)holder;
viewHolder.Initialize(((RecyclerViewHeaderItem)item).SectionName);
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams)holder.ItemView.LayoutParameters;
layoutParams.FullSpan = true;
}
}
public override void GetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
{
// STACKOVERFLOW NOTE: _headerTypeId is set to 0 when the method is called
int pos = parent.GetChildAdapterPosition(view);
// apply top spacing to first header only if there is one
if (pos == 0 && _headerTypeId != -1) {
outRect.Top = 2 * _spacing;
}
// these are fixed spacings for all cells
outRect.Bottom = 2 * _spacing;
outRect.Left = _spacing;
outRect.Right = _spacing;
// only apply spacing to the top row if we don't have headers
if (_headerTypeId == -1) {
// adjust the position index to account for headers
for (int i = 0; i < pos; i++) {
if (parent.GetAdapter().GetItemViewType(i) == _headerTypeId) {
pos--;
}
}
// apply top spacing to only the top row of cells
if (pos < _layoutManager.SpanCount) {
outRect.Top = 2 * _spacing;
}
}
}
So I was able to fix this by simply setting the height of the header view to wrap content instead of having it set to a value in the axml (32dp in my case). Thanks to Eugen Pechanec.
change
outRect.Top = 2 * _spacing;
to
outRect.Top =_spacing;
if that is not working just comment that line and try.

RecyclerView: add footer with item decoration?

Is that possible to add footer with item decoration, and not using adapter? Since I'm working with a very complex adapter with already lot of different viewholder types, I'd like to add an identical footer seamlessly to every list in my app.
As far as i can tell, this inst't best practice.
Here's description from RecyclerView.ItemDecoration 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.
However You can set specific behaviour when implementing your own divider based on adapter viewtype divider has to deal with. Here's an example code i used in one online course:
public class Divider extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mOrientation;
public Divider(Context context, int orientation) {
mDivider = ContextCompat.getDrawable(context, R.drawable.divider);
if (orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("This Item Decoration can be used only with a RecyclerView that uses a LinearLayoutManager with vertical orientation");
}
mOrientation = orientation;
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawHorizontalDivider(c, parent, state);
}
}
private void drawHorizontalDivider(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left, top, right, bottom;
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
//here we check the itemViewType we deal with, you can implement your own behaviour for Footer type.
// In this example i draw a drawable below every item that IS NOT Footer, as i defined Footer as a button in view
if (Adapter.FOOTER != parent.getAdapter().getItemViewType(i)) {
View current = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) current.getLayoutParams();
top = current.getTop() - params.topMargin;
bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}
}
Or you can use a library called Flexible Divider that allows for using a custom drawable or resource to be set.

Margin/padding in last Child in RecyclerView

I'm trying to add Padding/Margin Bottom in the last row and Padding/Margin Top in the first row. I can not do it in the item xml as it would affect all of my Children.
I have headers and children in my RecyclerView Adapter so I can not use the
android:padding="4dp"
android:clipToPadding="false"
I need to use it individually on the last first row of each header
This issue is even easier to solve. You can apply necessary padding to the RecylerView itself and set clipToPadding to false, otherwise, the padding will chop off your scrolling area. Here is an example
<android.support.v7.widget.RecyclerView
android:padding="4dp"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
See the padding will add 4dp on all sides including top and bottom. Then the clipToPadding parameter makes sure your child items are not chopped off. Now, add 4dp padding to all sides for your child items, and you are good to go. In total you get 8dp padding on sides and between items.
Instead of adding padding to both the top and bottom items, You can just add the padding to the top and bottom of your RecyclerView and set the clipToPadding attribute to false.
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_tpf"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingBottom="100dp" />
Add android:clipToPadding="false" and android:paddingBottom="100dp" in your recyclerview.
use ItemDecoration:
private 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) {
int position = parent.getChildAdapterPosition(view);
boolean isLast = position == state.getItemCount()-1;
if(isLast){
outRect.bottom = space;
outRect.top = 0; //don't forget about recycling...
}
if(position == 0){
outRect.top = space;
// don't recycle bottom if first item is also last
// should keep bottom padding set above
if(!isLast)
outRect.bottom = 0;
}
}
}
and
//8dp as px
int space = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
getResources().getDisplayMetrics()); // calculated
//int space = getResources().getDimensionPixelSize(
// R.dimen.list_item_padding_vertical); // from resources
recyclerView.addItemDecoration(new SpacesItemDecoration(space));
Add android:clipToPadding="false" and android:paddingBottom="65dp" in your recyclerview. If you are using fab menu button and actions on recycler view cell.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/dinner_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingBottom="65dp"/>
I use this in kotlin to give bottom margin to last item only
override fun onBindViewHolder(holder: RecyclerView.ViewHolder(view), position: Int) {
if (position == itemsList.lastIndex){
val params = holder.itemView.layoutParams as FrameLayout.LayoutParams
params.bottomMargin = 100
holder.itemView.layoutParams = params
}else{
val params = holder.itemView.layoutParams as RecyclerView.LayoutParams
params.bottomMargin = 0
holder.itemView.layoutParams = params
}
//other codes ...
}
For some reason the old clipToPadding=false solution isn't working for me. So I added an ItemDecoration
https://gist.github.com/kassim/582888fa5960791264fc92bc41fb6bcf
public class BottomPaddingDecoration extends RecyclerView.ItemDecoration {
private final int bottomPadding;
public BottomPaddingDecoration(int bottomPadding) {
this.bottomPadding = bottomPadding;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (position == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 0, 0, bottomPadding);
}
}
}
Java equivalent to #Radesh answer:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
if (position == itemsList.size() - 1) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams();
params.bottomMargin = 100;
holder.itemView.setLayoutParams(params);
} else {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams();
params.bottomMargin = 0;
holder.itemView.setLayoutParams(params);
}
}
I have modified amazing answer #snachmsm answer for better and give you idea how to use
properly
public class SpacesItemDecoration extends DividerItemDecoration {
private int space;
public SpacesItemDecoration(Context clContext,int oriantation,int space) {
super(clContext,oriantation);
this.space = space;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect,view,parent,state);
int position = parent.getChildAdapterPosition(view);
boolean isLast = position == state.getItemCount()-1;
if(isLast){
outRect.bottom = space;
outRect.top = 0; //don't forget about recycling...
}
/* if(position == 0){
outRect.top = space;
// don't recycle bottom if first item is also last
// should keep bottom padding set above
if(!isLast)
outRect.bottom = 0;
}*/
}
}
Long story short :
int freeSpaceAtBottom = 100; // the bottom free space in pixels
myRecyclerView.setClipToPadding(false);
myRecyclerView.setPadding(0,0,0,freeSpaceAtBottom);
setClipToPadding Sets whether this list view will clip its children to its padding and resize (but not clip) any EdgeEffect to the padded region, if padding is present. (1)

Categories

Resources