Add space to the end of the RecyclerView
Asked Answered
C

5

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>
Crwth answered 18/4, 2015 at 16:58 Comment(1)
G
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);
}
Glyptography answered 18/4, 2015 at 17:52 Comment(4)
try my solution, much simpler.Analemma
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.Tantalic
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.Headquarters
I've made a solution that will work well on each of those cases, based on something I've found of Google itself: https://mcmap.net/q/188384/-android-add-spacing-below-last-element-in-recyclerview-with-gridlayoutmanager github.com/AndroidDeveloperLB/FastScrollerAndRecyclerViewFixesVeliavelick
A
206
<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="50dp"
    android:clipToPadding="false"
    />
Analemma answered 18/4, 2015 at 18:11 Comment(11)
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.Analemma
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 :PGrossman
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.Paulinapauline
This should be the correct answer... simple, clean and without complications.Alburga
android:clipToPadding="false" is must.Pontic
This is perfect.Shamefaced
Precise and to the point answers are always appreciated. This should be the accepted answer.Kilogrammeter
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 hackyPublius
This works with any scrolling container, not just RecyclerView.Nitroglycerin
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.Borroff
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.Veliavelick
G
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);
}
Glyptography answered 18/4, 2015 at 17:52 Comment(4)
try my solution, much simpler.Analemma
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.Tantalic
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.Headquarters
I've made a solution that will work well on each of those cases, based on something I've found of Google itself: https://mcmap.net/q/188384/-android-add-spacing-below-last-element-in-recyclerview-with-gridlayoutmanager github.com/AndroidDeveloperLB/FastScrollerAndRecyclerViewFixesVeliavelick
K
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

Koine answered 18/1, 2020 at 21:21 Comment(1)
it is work for me , this code does exactlyCollarbone
C
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"
    />
Covarrubias answered 18/4, 2015 at 17:24 Comment(0)
A
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);
}
Arrowwood answered 15/1, 2021 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.