How to put RecyclerView inside NestedScrollView?

AndroidAndroid RecyclerviewNestedscrollview

Android Problem Overview


With creation of NestedScrollView you can put scrolling view inside another scrolling view as long as those implement NestedScrollingChild and NestedScrollingParent correctly.

(This is not bad design pattern "Ian Lake (from Google) actually recommends putting a RecyclerView inside a nestedscrollview here: plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T")

I want to put RecyclerView inside NestedScrollView and fortunately RecyclerView implements NestedScrollingChild so you can put it inside NestedScrollView.

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild

I have read these posts:

How to use RecyclerView inside NestedScrollView?

NestedScrolling with NestedScrollView, RecyclerView (Horizontal), inside a CoordinatorLayout

But the problem with most voted solution is, it calls all of the items of RecyclerView so for example if it is an endless RecyclerView and when the user reaches the end of the list you want to make a network request then with that solution the RecyclerView calls server repeatedly because it automatically reaches the last item of RecyclerView.

Anyway, how to set parameter so I can put RecyclerView inside NestedScrollView.(actually I want to put a viewgroup like framelayout or relativelayout as a single childe of nestedscrollview and then I want to put recyclerview inside framelayout or relativelayout)

When I put RecyclerView inside NestedScrollView there is nothing to display.


In order to create a sample project you can use cheesesquare and change the CheeseDetailActivity to have a RecyclerView.


Although the answer of BNK is not correct but BNK has tried a lot. So I award him the bounty. Still looking for nice solution....

Android Solutions


Solution 1 - Android

The following is my new updated answer:

<android.support.v4.widget.NestedScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.CardView
                android:id="@+id/cardview1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/card_margin">

                <LinearLayout
                    style="@style/Widget.CardContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Info CardView1"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/cheese_ipsum" />

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:id="@+id/cardview2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/cardview1"
                android:layout_margin="@dimen/card_margin">

                <LinearLayout
                    style="@style/Widget.CardContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Info CardView2"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/cheese_ipsum" />

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/cardview2"
                android:clipToPadding="false"
                android:paddingTop="0dp"/>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

In Activity:

        RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(true); // true: with header
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);            
        final MyLinearLayoutManager layoutManager = new MyLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false, getScreenHeight(this));
        // final CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(recyclerViewAdapter);  
        // recyclerView.setNestedScrollingEnabled(false); // Disables scrolling for RecyclerView, however, CustomLinearLayoutManager used instead of MyLinearLayoutManager

I have also updated to My GitHub's sample project

Screenshot:

enter image description here


Solution 2 - Android

Here is solution to call server only when you are realy need to load more data. In this way you can put your endless RecyclerView and many other views inside NestedScrollView. For me it is working well.

1. Create EndlessParentScrollListener class to handle scroll events from NestedSrollView.

public abstract class EndlessParentScrollListener implements NestedScrollView.OnScrollChangeListener {
        // The current offset index of data you have loaded
        private int currentPage = 0;
        // The total number of items in the dataset after the last load
        private int previousTotalItemCount = 0;
        // True if we are still waiting for the last set of data to load.
        private boolean loading = true;
        // Sets the starting page index
        private int startingPageIndex = 0;
        // The minimum amount of pixels to have below your current scroll position
        // before loading more.
        private int visibleThresholdDistance = 300;

        RecyclerView.LayoutManager mLayoutManager;

        public EndlessParentScrollListener(RecyclerView.LayoutManager layoutManager) {
            this.mLayoutManager = layoutManager;
        }

        @Override
        public void onScrollChange(NestedScrollView scrollView, int x, int y, int oldx, int oldy) {
            // We take the last son in the scrollview
            View view = scrollView.getChildAt(scrollView.getChildCount() - 1);
            int distanceToEnd = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

            int totalItemCount = mLayoutManager.getItemCount();
            // If the total item count is zero and the previous isn't, assume the
            // list is invalidated and should be reset back to initial state
            if (totalItemCount < previousTotalItemCount) {
                this.currentPage = this.startingPageIndex;
                this.previousTotalItemCount = totalItemCount;
                if (totalItemCount == 0) {
                    this.loading = true;
                }
            }

            // If it’s still loading, we check to see if the dataset count has
            // changed, if so we conclude it has finished loading and update the current page
            // number and total item count.
            if (loading && (totalItemCount > previousTotalItemCount)) {
                loading = false;
                previousTotalItemCount = totalItemCount;
            }

            // If it isn’t currently loading, we check to see if we have breached
            // the visibleThreshold and need to reload more data.
            // If we do need to reload some more data, we execute onLoadMore to fetch the data.
            // threshold should reflect how many total columns there are too
            if (!loading && distanceToEnd <= visibleThresholdDistance) {
                currentPage++;
                onLoadMore(currentPage, totalItemCount);
                loading = true;
            }
        }

        // Defines the process for actually loading more data based on page
        public abstract void onLoadMore(int page, int totalItemsCount);
    }

2. Set listener

private void initRecycler() {  
 		//TODO init recycler adapter here

		recycler.setNestedScrollingEnabled(false);    		
		LinearLayoutManager _layoutManager = new LinearLayoutManager(this);
		recycler.setLayoutManager(_layoutManager);
		NestedScrollView scrollView = (NestedScrollView) findViewById(R.id.scrollView);
		scrollView.setOnScrollChangeListener(new EndlessParentScrollListener(_layoutManager) {
    			@Override
    			public void onLoadMore(int page, int totalItemsCount) {        				
    				if (loadedItemCount < serverItemsCount)
    					customLoadMoreDataFromApi();
    			}
    		});
        customLoadMoreDataFromApi();
    }

Short xml if someone find it usefull:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/background_light"
        android:fitsSystemWindows="true">


        <android.support.design.widget.AppBarLayout>
            ...
        </android.support.design.widget.AppBarLayout>

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/scrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            android:scrollbars="vertical"
            android:scrollbarAlwaysDrawVerticalTrack="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:scrollbarAlwaysDrawVerticalTrack="false"
                android:scrollbars="vertical">
                
                <!-- some views goes here-->
                
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recyclerFeed"
                    android:scrollbars="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

                <!-- and possibly here-->

            </LinearLayout>
        </android.support.v4.widget.NestedScrollView>

    </android.support.design.widget.CoordinatorLayout>

Solution 3 - Android

So put RecyclerView inside NestedScrollView directly will unfortunately display nothing. However, there is a way to put the recyclerview inside the NestedScrollView indirectly - just use a frameLayout as the third party to hold your recyclerview.

This is the framelayout which holds the nested recyclerview in your activity class:

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".ExampleFragment"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            >
        </android.support.v4.widget.NestedScrollView>
   </FrameLayout>

Put your fragment into the framelayout (code sits within the activity class):

 ExampleFragment exampleFragment = new ExampleFragment();

    FragmentManager fm = getSupportFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    ft.add(R.id.container, exampleFragment);
    ft.commit();

In your exampleFragment, you can then put your recyclerview.

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/post_container"
        android:background="#E0E0E0">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/my_recycler_view"
                android:scrollbars="vertical"

                android:layout_width="match_parent"
                android:layout_height="match_parent">
            </android.support.v7.widget.RecyclerView>


    </RelativeLayout>

This is the fragment code:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    super.onCreateView(inflater, container, savedInstanceState);
            llLayout = (RelativeLayout) inflater.inflate(R.layout.example_fragment_layout, container, false);
                    mRecyclerView = (RecyclerView) llLayout.findViewById(R.id.my_recycler_view);

The following is the CheeseSquare XML layout you should have:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/detail_backdrop_height"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp">

            <ImageView
                android:id="@+id/backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

   <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".ExampleFragment"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            >
        </android.support.v4.widget.NestedScrollView>
   </FrameLayout>

    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        android:src="@drawable/ic_discuss"
        android:layout_margin="@dimen/fab_margin"
        android:clickable="true"/>

</android.support.design.widget.CoordinatorLayout>

Solution 4 - Android

I have wasted at least a week behind this and the only solution that works is to remove the nestedscrollview. I know that is not the answer to this particular question of adding recyclerview inside nestedscrollview, but to make the recyclerview to actually recycle the view, you HAVE TO REMOVE the nestedscrollview. If you happen to have multiple view inside instead of just the recyclerview, You have to add them as items of the recyclerview by setting different viewholders, and then adding

app:layout_behavior="@string/appbar_scrolling_view_behavior"

to the recyclerview or the parent of recyclerview(the parent cannot have any other view other than Recyclerview for this to work).

In my case, the requirement was as follows:

  <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:fitsSystemWindows="true"
        app:layout_constraintTop_toTopOf="parent">

        <!-- Scrollable view here -->

        <com.google.android.material.appbar.AppBarLayout
            ....   >

            <com.google.android.material.appbar.CollapsingToolbarLayout
                ....>

                <androidx.appcompat.widget.Toolbar
                    ....>

                    <include
                        android:id="@+id/toolbar_header_view"
                        .... />
                </androidx.appcompat.widget.Toolbar>

                <include layout="@layout/widget_header" />
            </com.google.android.material.appbar.CollapsingToolbarLayout>
        </com.google.android.material.appbar.AppBarLayout>

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            ....>
            <LinearLayout orientation="vertical" ....>
                <View .../>  <!-- I needed to have these two views along with recyclerview and i wanted them to do nestedscroll along with recyclerview -->
                <View .../>
                <androidx.recyclerview.widget.RecyclerView
                .... />
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

I needed to have two views before recyclerview and I wanted them to do nestedscroll along with recyclerview. The only thing that worked was by removing these to views and adding them as the first two items of recyclerview, and then adding

app:layout_behavior="@string/appbar_scrolling_view_behavior"

to the swipetorefresh layout

Hope this helps someone. I will add more info If required.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmmloolooView Question on Stackoverflow
Solution 1 - AndroidBNKView Answer on Stackoverflow
Solution 2 - Androidandre719mvView Answer on Stackoverflow
Solution 3 - AndroidSimonView Answer on Stackoverflow
Solution 4 - Androidhushed_voiceView Answer on Stackoverflow