Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pagination with RecyclerView within NestedScrollView

I know it's old but I really tired from searching and trying a lot of solutions from here or from outside web pages

the problem is: when to invoke data from API to a Vertical RecyclerView it loads all data without any consideration to Pagination

which work successfully with the horizontal Recycler view

and without any scrolling or screen touch

NestedScroll View contain 2 Recycler in the following order

NestedScrollView as parent
HorizontalRecyclerView as child
VerticalRecyclerView as child

my XML file :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.organizers_group.stadfm.Activities.NewsFeedHome"
tools:showIn="@layout/app_bar_news_feed_home">

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/_5dp"
        android:paddingBottom="@dimen/_7dp">

        <RelativeLayout
            android:id="@+id/linearLayout2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0">

            <TextView
                android:id="@+id/horizontalTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/trending_stories"
                android:layout_marginStart="@dimen/_10dp"
                android:paddingTop="@dimen/_10dp"
                android:paddingBottom="@dimen/_10dp"
                android:fontFamily="@font/avenir_heavy"
                android:textColor="@color/trending_stories_color"
                android:textSize="@dimen/_20sp"/>

            <io.supercharge.shimmerlayout.ShimmerLayout
                android:id="@+id/shimmer_horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_below="@+id/horizontalTitle"
                app:shimmer_animation_duration="1200">

                <!-- Adding rows of placeholders -->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">
                    <include layout="@layout/place_holder" />
                    <include layout="@layout/place_holder" />
                    <include layout="@layout/place_holder" />

                </LinearLayout>

            </io.supercharge.shimmerlayout.ShimmerLayout>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/horizontalRecycler"
                android:layout_width="match_parent"
                android:layout_below="@+id/trending_story"
                android:layout_height="wrap_content"
                android:orientation="horizontal" />

            <ProgressBar
                android:id="@+id/pBLoading"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:indeterminate="true"
                android:visibility="invisible"
                android:layout_alignParentBottom="true"
                style="?android:attr/progressBarStyleHorizontal" />
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/linearLayout2"
            android:orientation="vertical"
            android:layout_margin="@dimen/_8dp"
            android:descendantFocusability="blocksDescendants"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/linearLayout2"
            app:layout_constraintVertical_bias="0.032"
            app:layout_constraintVertical_weight="1">

            <LinearLayout
                android:id="@+id/storiesLayout"
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/_10dp"
                    android:paddingBottom="@dimen/_5dp"
                    android:text="@string/your_stories"
                    android:fontFamily="@font/avenir_heavy"
                    android:textColor="@color/stories_color"
                    android:textSize="@dimen/_20sp" />

                <ImageView
                    android:id="@+id/moreStoriesImg"
                    android:layout_width="@dimen/_40dp"
                    android:layout_height="@dimen/_40dp"
                    android:src="@drawable/more"
                    android:layout_gravity="center"/>
            </LinearLayout>

            <io.supercharge.shimmerlayout.ShimmerLayout
                android:id="@+id/shimmer_vertical"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/story_layout"
                android:orientation="vertical"
                app:shimmer_animation_duration="1200">

                <!-- Adding placeholders -->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <include layout="@layout/place_holder" />
                    <include layout="@layout/place_holder" />
                </LinearLayout>

            </io.supercharge.shimmerlayout.ShimmerLayout>

            <!-- problem is with the following recycler-->
            <android.support.v7.widget.RecyclerView
                android:id="@+id/verticalRecycler"
                android:layout_width="match_parent"
                android:layout_below="@+id/story_layout"
                android:layout_height="wrap_content"
                android:nestedScrollingEnabled="false"
                app:layoutManager="android.support.v7.widget.LinearLayoutManager"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                />

            <ProgressBar
                android:id="@+id/pBLoading"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:indeterminate="true"
                android:visibility="invisible"
                android:layout_alignParentBottom="true"
                style="?android:attr/progressBarStyleHorizontal" />
        </RelativeLayout>
    </RelativeLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>

EndlessRecyclerViewScrollListener class (from stackOverFlow)

package com.organizers_group.stadfm.Adapters;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;

public abstract class EndlessRecyclerViewScrollListener extends 
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 5;
// The current offset index of data you have loaded
private int currentPage = 1;
// 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 = 1;

RecyclerView.LayoutManager mLayoutManager;

public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
    this.mLayoutManager = layoutManager;
}

public EndlessRecyclerViewScrollListener(GridLayoutManager layoutManager) {
    this.mLayoutManager = layoutManager;
    visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}

public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
    this.mLayoutManager = layoutManager;
    visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}

public int getLastVisibleItem(int[] lastVisibleItemPositions) {
    int maxSize = 0;
    for (int i = 0; i < lastVisibleItemPositions.length; i++) {
        if (i == 0) {
            maxSize = lastVisibleItemPositions[i];
        }
        else if (lastVisibleItemPositions[i] > maxSize) {
            maxSize = lastVisibleItemPositions[i];
        }
    }
    return maxSize;
}

// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
    int lastVisibleItemPosition = 0;
    int totalItemCount = mLayoutManager.getItemCount();

    if (mLayoutManager instanceof StaggeredGridLayoutManager) {
        int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
        // get maximum element within the list
        lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
    } else if (mLayoutManager instanceof GridLayoutManager) {
        lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
    } else if (mLayoutManager instanceof LinearLayoutManager) {
        lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
    }

    // If the total item count is zero and the previous isn't, assume the
    // list is invalidated and should be reset back to the 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 && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
        currentPage++;
        onLoadMore(currentPage, totalItemCount, view);
        loading = true;
    }
}

// Call this method whenever performing new searches
public void resetState() {
    this.currentPage = this.startingPageIndex;
    this.previousTotalItemCount = 0;
    this.loading = true;
}

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

my activity :

    RecyclerView horizontalRecyclerView = findViewById(R.id.horizontalRecycler);
    horizontalRecyclerView.setHasFixedSize(true);
    LinearLayoutManager horizontalLayoutManager = new LinearLayoutManager(this , LinearLayoutManager.HORIZONTAL , false);
    horizontalRecyclerView.setLayoutManager(horizontalLayoutManager);

    // get trending post
    final int trendingPage = 1;
    myCustomAdapter = new MyCustomAdapter( this , getPosts(Page));
    horizontalRecyclerView.setAdapter(myCustomAdapter);
    myCustomAdapter.notifyDataSetChanged();

    EndlessRecyclerViewScrollListener scrollListener = new EndlessRecyclerViewScrollListener(horizontalLayoutManager) {
        @Override
        public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
            // Triggered only when new data needs to be appended to the list
            // Add whatever code is needed to append new items to the bottom of the list
            showProgressView(ProgressBar);
            getPosts(page);
        }
    };
    // Adds the scroll listener to RecyclerView
    horizontalRecyclerView.addOnScrollListener(scrollListener);
    // End Of horizontalRecyclerView

    // Stories RecyclerView
    RecyclerView verticalRecycler = findViewById(R.id.verticalRecycler);
    verticalRecycler.setHasFixedSize(true);
    verticalRecycler.setNestedScrollingEnabled(false);
    LinearLayoutManager verticalLayoutManager = new LinearLayoutManager(this , LinearLayoutManager.VERTICAL , false);
    verticalRecycler.setLayoutManager(verticalLayoutManager);

    // get mixed posts
    verticalAdapter = new VerticalAdapter( this , getStoriesPosts(1));
    verticalRecycler.setAdapter(verticalAdapter);
    verticalAdapter.notifyDataSetChanged();

    EndlessRecyclerViewScrollListener mixedScrollListener = new EndlessRecyclerViewScrollListener(verticalLayoutManager) {
        @Override
        public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
            // Triggered only when new data needs to be appended to the list
            // Add whatever code is needed to append new items to the bottom of the list
            showProgressView(progressBar);
            getStoriesPosts(page);
        }
    };
    // Adds the scroll listener to RecyclerView
    storiesRecyclerView.addOnScrollListener(mixedScrollListener);
    // End Of Mixed Stories

please help me!
how to do pagination with that vertical RecyclerView?

any bit of help is very appreciated
Thanks in advance.

like image 307
Muhamad Jalal Avatar asked Dec 30 '25 02:12

Muhamad Jalal


1 Answers

to solve this challenge I perform the following Steps:

1- for XML file or EndlessRecyclerViewScrollListener NO CHANGES

2- for my activity I delete EndlessRecyclerViewScrollListener for vertical Recycler and defined my NestedScrollListener instance then set OnScrollChangeListener as below:

    boolean loading = true;
    int pageNumber = 1;
    NestedScrollView scroller = findViewById(R.id.nestedScroll);

    if (scroller != null) {

        scroller.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {

            if (scrollY > oldScrollY) {
                Log.i(TAG, "Scroll DOWN");
            }
            if (scrollY < oldScrollY) {
                Log.i(TAG, "Scroll UP");
            }

            if (scrollY == 0) {
                Log.i(TAG, "TOP SCROLL");
            }

            if (scrollY == ( v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight() )) {
                Log.i(TAG, "BOTTOM SCROLL");
              // here where the trick is going
                if (loading){
                    pageNumber++;
                    showProgressView(mixedProgressBar);
                    // calling from adapter addToExistingList(list)
                    // with the defined Adapter instance
                    verticalAdapter.addToExistingList(getPosts(pageNumber));
                    // reset the boolean(loading) to prevent
                    // auto loading data from APi
                    loading = false;
                }
            }
        });
    }

3- for VerticalAdapter, I add the following method :

public void addToExistingList(List<DataModel> dataModelList){
    // update oldDataModelList with newer data from pagination
    oldDataModelList.retainAll(dataModelList);
    notifyItemRangeChanged(oldDataModelList.size()-1 , dataModelList.size());
}

4- for getPosts(pageNumber) on fetching data success re-assign the boolean(loading)

getPosts(pageNumber) is a method to invoke data from API

JsonArrayRequest arrayRequest = new JsonArrayRequest(Request.Method.GET,baseAPI,
         response1 -> {
                try {
                       // doing stuff to invoke your data
                        // re-assign loading variable to load again
                        loading = true;

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
like image 146
Muhamad Jalal Avatar answered Jan 01 '26 14:01

Muhamad Jalal