55

I have a recyclerview with a gridlayout. What I want is when the user scrolls to the end of the list (see my bad mockup), there should be an empty space with a height of 50dp, which isn't the same dimensions as my grid.

Note that this space is only visible at the very end end, as I do not want to change the layout. I could make it so that the recycerview has a margin bottom of 50dp, but I do not want to do that.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fab="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:scrollbars="vertical"
        />

</RelativeLayout>
Adam S
  • 16,144
  • 6
  • 54
  • 81
HaloMediaz
  • 1,251
  • 2
  • 13
  • 31

5 Answers5

204
<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="50dp"
    android:clipToPadding="false"
    />
mbmc
  • 5,024
  • 5
  • 25
  • 53
  • 7
    Why the down vote? This code does exactly what the OP asked for: add a space at the bottom only when reaching the end of the list. Please check the code or provide an explanation before down voting. – mbmc Apr 18 '15 at 22:17
  • 5
    I guess you was down voted because it will also eat the space at the bottom of recycler view. he need space at bottom but while scrolling content should come from bottom, padding will make it appear from 50dp above. I am not the down voter though :P – Akhil Dad Feb 08 '16 at 13:51
  • This worked for me as well, upvoted. It adds padding to the last item, but not to the whole RecyclerView. Works well even after adding a new item or removing an item from the view. – Makotosan Apr 04 '16 at 15:04
  • 1
    This should be the correct answer... simple, clean and without complications. – Mariano Zorrilla May 30 '16 at 02:44
  • 30
    android:clipToPadding="false" is must. – Melbourne Lopes Oct 26 '16 at 09:23
  • This is perfect. – Akshar Patel Nov 14 '17 at 15:34
  • Precise and to the point answers are always appreciated. This should be the accepted answer. – Syed Muntazir Mehdi Feb 20 '18 at 05:26
  • 17
    You also need to add ```android:scrollbarStyle="outsideOverlay"``` so that the scrollbar goes all the way to the bottom. Otherwise it stops above the bottom and looks hacky – Carson Holzheimer Apr 03 '18 at 05:53
  • This works with any scrolling container, not just RecyclerView. – Menny Jul 16 '19 at 19:48
  • This is not the best solution if you have fading edges enabled in the RecyclerView, since the edges rendering will have an offset with the RecyclerView's content. But if you don't, yes, it's pretty straightforward. – Rui Mar 30 '20 at 07:44
  • This works in general, but it has issues when you try to have more advanced stuff, like handling transparent navigation bar or having customized fast-scroller using itemDecoration. – android developer Jan 23 '21 at 09:04
52

This is best achieved with an item decoration.

Here's an example that works with a LinearLayoutManager - you'll have to adjust to suit for your Grid layout. What it does is checks each item to see if it's the last one, and if it is it adds the offset to the bottom of it. For a Grid layout, the hard part is figuring out whether your item position is in the last row or not.

// After setting layout manager, adapter, etc...
float offsetPx = getResources().getDimension(R.dimen.bottom_offset_dp);
BottomOffsetDecoration bottomOffsetDecoration = new BottomOffsetDecoration((int) offsetPx);
mRecyclerView.addItemDecoration(bottomOffsetDecoration);

...

static class BottomOffsetDecoration extends RecyclerView.ItemDecoration {
    private int mBottomOffset;

    public BottomOffsetDecoration(int bottomOffset) {
        mBottomOffset = bottomOffset;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int dataSize = state.getItemCount();
        int position = parent.getChildAdapterPosition(view);
        if (dataSize > 0 && position == dataSize - 1) {
            outRect.set(0, 0, 0, mBottomOffset);
        } else {
            outRect.set(0, 0, 0, 0);
        }

    }
}

For a GridLayoutManager, inside the getItemOffsets method you could do something similar to this to figure out if it's the last row:

GridLayoutManager grid = (GridLayoutManager)parent.getLayoutManager();
if ((dataSize - position) <= grid.getSpanCount()) {
    outRect.set(0, 0, 0, mBottomOffset);
} else {
    outRect.set(0, 0, 0, 0);
}
Mun0n
  • 4,438
  • 4
  • 28
  • 46
Adam S
  • 16,144
  • 6
  • 54
  • 81
  • 1
    try my solution, much simpler. – mbmc Apr 18 '15 at 18:25
  • 5
    This solution only works if the number of items doesn't change. When a new item is added to the end, both the new and the second last item will have that bottom spacing. – Gubbel Oct 19 '15 at 15:46
  • 1
    Great method, thanks. Just a caveat on the GridLayoutManager code, this only works if the last row is "full", e.g. a grid with 8 columns and there are 8 items on the last row. If it's not, some items on the previous row will also be assigned the bottom padding. The solution for me was to implement a more involved `isOnLastRow()` method. – David Ferrand May 10 '19 at 14:31
  • I've made a solution that will work well on each of those cases, based on something I've found of Google itself: https://stackoverflow.com/a/65858514/878126 https://github.com/AndroidDeveloperLB/FastScrollerAndRecyclerViewFixes – android developer Jan 23 '21 at 11:25
10

I had the similar issue. After reading all others replies and I found the changes in layout xml for recyclerview worked for my recycler view as expected:

        android:paddingBottom="127dp"
        android:clipToPadding="false"
        android:scrollbarStyle="outsideOverlay"  

The complete layout looks like:

<android.support.v7.widget.RecyclerView
        android:id="@+id/library_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="160dp"
        android:layout_marginEnd="160dp"
        tools:listitem="@layout/library_list_item" />  

For the effect of before and after see the link at androidblog.us: Adding Space to End of Android Recylerview
Let me know how it works for you.

David

Boken
  • 4,825
  • 10
  • 32
  • 42
us_david
  • 4,431
  • 35
  • 29
0

You can try the below code, remember "I do not test this code"

public class MyRecyclerView extends RecyclerView {
    private Context context;
    public MyRecyclerView(Context context) {
        super(context);
        this.context = context;

    }

    public MyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyle)     {
        super(context, attrs, defStyle);
        this.context = context;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        View view = (View) getChildAt(getChildCount()-1);
        int diff = (view.getBottom()-(getHeight()+getScrollY()+view.getTop()));
        if( diff == 0 ){  // if diff is zero, then the bottom has been reached
            TextView tv = new TextView(context);
            tv.setHeight(dpToPx(50));
            addView(tv,getChildCount());//update --> add to last
            requestLayout();
        }
        super.onScrollChanged(l, t, oldl, oldt);
    }
    public int dpToPx(int dp) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
        return px;
    }
}

and in layout:

<your_packagae.MyRecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"
    />
Robust
  • 2,415
  • 2
  • 22
  • 37
0

Make a class name with BottomOffsetDecoration

public class BottomOffsetDecoration extends RecyclerView.ItemDecoration {
        private int mBottomOffset;
    
        public BottomOffsetDecoration(int bottomOffset) {
            mBottomOffset = bottomOffset;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int dataSize = state.getItemCount();
            int position = parent.getChildAdapterPosition(view);
            if (dataSize > 0 && position == dataSize - 1) {
                outRect.set(0, 0, 0, mBottomOffset);
            } else {
                outRect.set(0, 0, 0, 0);
            }
    
        }
    }

then Add these line after adding adapter and layoutmanager to recyclerview

float offsetPx = getResources().getDimension(R.dimen.bottom_offset_dp);
        BottomOffsetDecoration bottomOffsetDecoration = new BottomOffsetDecoration((int) offsetPx);
        rv.addItemDecoration(bottomOffsetDecoration);

and for GridLayout Add these lines after assigning recyclerview layout manager

GridLayoutManager grid = (GridLayoutManager)parent.getLayoutManager();
if ((dataSize - position) <= grid.getSpanCount()) {
    outRect.set(0, 0, 0, mBottomOffset);
} else {
    outRect.set(0, 0, 0, 0);
}