This article provides you with a complete solution of pagination of horizontal RecyclerView along with source code. We tried to give our best solution for horizontal RecyclerView pagination. If you feel any improvement please put your comment inside comment box.
Prerequisite
In this demo, I will use RecyclerView, Horizontal SwipeRefreshLayout, Retrofit, Rest API with pagination.
- RecyclerView – You have to deep knowledge of RecyclerView and how to use it. If not, Read our article on best practices of RecyclerView.
- HorizontalSwipeRefreshLayout -You should enough understanding of SwipeRefreshLayout. I have created a custom component that works same as SwipeRefreshLayout in horizontal manner. For that read our previous article
Implementation Step of Pagination on Horizontal RecyclerView
- Create and setup projects
- Write a POJO class for list item
- Modify the activity_main.xml and HorizontalSwipeRefreshLayout and RecyclerView
- For spacing between two items write a space divider
- Prepare Retrofit client for REST API calls
- Create a layout file for list item view
- Writes a RecyclerView adapter for holding recycler item
- In MainActviity set adapter and RecyclerView and manage pagination on horizontal scroll
Let’s follow above step
Pagination on Horizontal RecyclerView android (Sample APP)
1. Create and setup projects
Let’s move the Android Studio and create a new project with EmptyActivity template. For do that go the file menu and create a new project fill info like name and package name.
2. Write a POJO class for list item
In project main folder create a new model class and create entity such as ID, Main Title, etc.
package com.wave.pagination.recyclerview.model; import com.google.gson.annotations.SerializedName; public class NewsItem { @SerializedName("created_at") private String mCreatedAt; @SerializedName("id") private String mId; @SerializedName("main_title") private String mMainTitle; @SerializedName("media_url") private String mMediaUrl; @SerializedName("updated_at") private String mUpdatedAt; public String getCreatedAt() { return mCreatedAt; } public void setCreatedAt(String createdAt) { mCreatedAt = createdAt; } public String getId() { return mId; } public void setId(String id) { mId = id; } public String getMainTitle() { return mMainTitle; } public void setMainTitle(String mainTitle) { mMainTitle = mainTitle; } public String getMediaUrl() { return mMediaUrl; } public void setMediaUrl(String mediaUrl) { mMediaUrl = mediaUrl; } public String getUpdatedAt() { return mUpdatedAt; } public void setUpdatedAt(String updatedAt) { mUpdatedAt = updatedAt; } }
3. Modify the activity_main.xml and HorizontalSwipeRefreshLayout and RecyclerView
Let’s move to next step open activity_main.xml and paste below code. For better understanding of HorizontalSwipeRefreshLayout read here.
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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="match_parent" android:background="@drawable/night_sky" tools:context=".MainActivity"> <com.wave.pagination.recyclerview.widget.HorizontalSwipeRefreshLayout android:id="@+id/swipeRefresh" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <android.support.v7.widget.RecyclerView android:id="@+id/mRecyclerView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </com.wave.pagination.recyclerview.widget.HorizontalSwipeRefreshLayout> </android.support.constraint.ConstraintLayout>
4. For spacing between two items write a space divider
EqualSpacingItemDecoration manages space between two views.
package com.wave.pagination.recyclerview.utils; import android.graphics.Rect; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; public class EqualSpacingItemDecoration extends RecyclerView.ItemDecoration { private final int spacing; private int displayMode; public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; public static final int GRID = 2; public EqualSpacingItemDecoration(int spacing) { this(spacing, -1); } public EqualSpacingItemDecoration(int spacing, int displayMode) { this.spacing = spacing; this.displayMode = displayMode; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildViewHolder(view).getAdapterPosition(); int itemCount = state.getItemCount(); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); setSpacingForDirection(outRect, layoutManager, position, itemCount); } private void setSpacingForDirection(Rect outRect, RecyclerView.LayoutManager layoutManager, int position, int itemCount) { // Resolve display mode automatically if (displayMode == -1) { displayMode = resolveDisplayMode(layoutManager); } switch (displayMode) { case HORIZONTAL: outRect.left = spacing; outRect.right = position == itemCount - 1 ? spacing : 0; outRect.top = spacing; outRect.bottom = spacing; break; case VERTICAL: outRect.left = spacing; outRect.right = spacing; outRect.top = spacing; outRect.bottom = position == itemCount - 1 ? spacing : 0; break; case GRID: if (layoutManager instanceof GridLayoutManager) { GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; int cols = gridLayoutManager.getSpanCount(); int rows = itemCount / cols; outRect.left = spacing; outRect.right = position % cols == cols - 1 ? spacing : 0; outRect.top = spacing; outRect.bottom = position / cols == rows - 1 ? spacing : 0; } break; } } private int resolveDisplayMode(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof GridLayoutManager) return GRID; if (layoutManager.canScrollHorizontally()) return HORIZONTAL; return VERTICAL; } }
5. Prepare Retrofit client for REST API calls
5.1 Create Retrofit API Service interface
package com.wave.pagination.recyclerview; import com.wave.pagination.recyclerview.model.NewsItem; import java.util.List; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface RestApiService { @GET("newsfeed.php") Call<List<NewsItem>> getNewsFeed(@Query("page_no") Integer pageNo); }
5.2 Prepare Retrofit client for API calls
package com.wave.pagination.recyclerview; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitInstance { private static final String BASE_URL = "https://androidwave.com/api/"; private static Retrofit retrofit = null; public static RestApiService getApiService() { if (retrofit == null) { retrofit = new Retrofit .Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit.create(RestApiService.class); } }
6. Create a layout file for list item view
6.1 Create a layout file named is list_item for showing the news items.
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="320dp" android:layout_height="match_parent" android:layout_gravity="center" android:layout_marginTop="32dp" android:layout_marginBottom="32dp" android:background="@color/fui_transparent" card_view:cardCornerRadius="10dp" card_view:cardElevation="5dp" > <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:background="#a1ffffff" > <TextView android:id="@+id/txtNewsTitle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:gravity="center" android:letterSpacing="-0.02" android:lineSpacingExtra="8sp" android:maxLines="3" android:textColor="@color/navy" android:textSize="18sp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" card_view:layout_constraintBottom_toTopOf="@+id/layoutWave" card_view:layout_constraintEnd_toEndOf="parent" card_view:layout_constraintStart_toStartOf="parent" tools:text="What is something that you swear happened but you have no proof and nobody believes you?" /> <android.support.constraint.ConstraintLayout android:id="@+id/layoutWave" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/txtNewsTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_default="spread" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txtNewsTitle" > <ImageView android:id="@+id/ivNewsImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:minHeight="60dp" android:scaleType="centerInside" android:src="@drawable/picture" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView>
6.2 Prepare view for loading view during API call
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="320dp" android:layout_height="match_parent" android:layout_gravity="center" android:layout_marginTop="32dp" android:layout_marginBottom="32dp" android:background="@color/fui_transparent" card_view:cardCornerRadius="10dp" card_view:cardElevation="5dp"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:background="#a1ffffff"> <ProgressBar android:id="@+id/progressBar2" style="?android:attr/progressBarStyle" android:layout_width="56dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView>
7. Writes a RecyclerView adapter for holding recycler item
In src folder create a new java class named is PaginationAdapter which extends RecyclerView.Adapter. than paste below code. In this adapter I’m using two views one for loading that will show when api call and second for showing list item
package com.wave.pagination.recyclerview; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.wave.pagination.recyclerview.model.NewsItem; import java.util.List; public class PaginationAdapter extends RecyclerView.Adapter<BaseViewHolder> { private static final int VIEW_TYPE_LOADING = 0; public static final int VIEW_TYPE_NORMAL = 1; private boolean isLoaderVisible = false; private List<NewsItem> mNewsItemList; private Callback mCallback; public PaginationAdapter(List<NewsItem> news) { this.mNewsItemList = news; } public void setCallback(Callback callback) { mCallback = callback; } @NonNull @Override public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_NORMAL: return new ViewHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false)); case VIEW_TYPE_LOADING: return new EmptyViewHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false)); default: return null; } } @Override public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) { holder.onBind(position); } @Override public int getItemViewType(int position) { if (isLoaderVisible) { return position == mNewsItemList.size() - 1 ? VIEW_TYPE_LOADING : VIEW_TYPE_NORMAL; } else { return VIEW_TYPE_NORMAL; } } @Override public int getItemCount() { return mNewsItemList == null ? 0 : mNewsItemList.size(); } public void add(NewsItem response) { mNewsItemList.add(response); notifyItemInserted(mNewsItemList.size() - 1); } public void addAll(List<NewsItem> postItems) { for (NewsItem response : postItems) { add(response); } } private void remove(NewsItem postItems) { int position = mNewsItemList.indexOf(postItems); if (position > -1) { mNewsItemList.remove(position); notifyItemRemoved(position); } } public void addLoading() { isLoaderVisible = true; add(new NewsItem()); } public void removeLoading() { isLoaderVisible = false; int position = mNewsItemList.size() - 1; NewsItem item = getItem(position); if (item != null) { mNewsItemList.remove(position); notifyItemRemoved(position); } } public void clear() { while (getItemCount() > 0) { remove(getItem(0)); } } NewsItem getItem(int position) { return mNewsItemList.get(position); } public interface Callback { void onRepoEmptyViewRetryClick(); } public class ViewHolder extends BaseViewHolder { TextView newTitle; ImageView newsImage; public ViewHolder(View itemView) { super(itemView); newTitle = itemView.findViewById(R.id.txtNewsTitle); newsImage = itemView.findViewById(R.id.ivNewsImage); } protected void clear() { } public void onBind(int position) { super.onBind(position); NewsItem mNewsItem = mNewsItemList.get(position); newTitle.setText(mNewsItem.getMainTitle()); /* Glide.with(itemView.getContext()) .load(mNewsItem.getMediaUrl()) .apply(new RequestOptions().override( ScreenUtils.getScreenWidth(itemView.getContext()) - ViewUtils.dpToPx(36), 150) .placeholder(R.drawable.picture) .error(R.drawable.picture)) .into(newsImage);*/ } } public class EmptyViewHolder extends BaseViewHolder { public EmptyViewHolder(View itemView) { super(itemView); } @Override protected void clear() { } } }
8. Create Scroll listener
Create abstract class named PaginationScrollListener. With the help of PaginationScrollListener we will manageapagination
package com.wave.pagination.recyclerview.utils; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener { LinearLayoutManager layoutManager; private static final int PAGE_SIZE = 10; /** * Supporting only LinearLayoutManager for now. * * @param layoutManager */ public PaginationScrollListener(LinearLayoutManager layoutManager) { this.layoutManager = layoutManager; } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int visibleItemCount = layoutManager.getChildCount(); int totalItemCount = layoutManager.getItemCount(); int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); if (!isLoading() && !isLastPage()) { if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0 && totalItemCount >= PAGE_SIZE) { loadMoreItems(); } } } protected abstract void loadMoreItems(); public abstract boolean isLastPage(); public abstract boolean isLoading(); }
9. Finally, Open the MainActivity and do following operation
- Bind the layout component using findViewById
- Create layout manager instance set orientation is HORIZONTAL
- Set adapter on RecyclerView
- Add scroll listener on RecyclerView
- Set HorizontalSwipeRefreshLayout listener
package com.wave.pagination.recyclerview; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import com.wave.pagination.recyclerview.model.NewsItem; import com.wave.pagination.recyclerview.utils.EqualSpacingItemDecoration; import com.wave.pagination.recyclerview.utils.PaginationScrollListener; import com.wave.pagination.recyclerview.widget.HorizontalSwipeRefreshLayout; import java.util.ArrayList; import java.util.List; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class MainActivity extends AppCompatActivity implements HorizontalSwipeRefreshLayout.OnRefreshListener { private static final String TAG = "MainActivity"; PaginationAdapter mPaginationAdapter; LinearLayoutManager mLayoutManager; RecyclerView mRecyclerView; HorizontalSwipeRefreshLayout mSwipeRefreshLayout; public static final int PAGE_START = 1; private int currentPage = PAGE_START; private boolean isLastPage = false; private int totalPage = 3; private boolean isLoading = false; int itemCount = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getSupportActionBar().hide(); mRecyclerView = findViewById(R.id.mRecyclerView); mSwipeRefreshLayout = findViewById(R.id.swipeRefresh); mSwipeRefreshLayout.setOnRefreshListener(this); mPaginationAdapter = new PaginationAdapter(new ArrayList<NewsItem>()); mRecyclerView.setHasFixedSize(true); mLayoutManager = new LinearLayoutManager(this); mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.addItemDecoration(new EqualSpacingItemDecoration(32, EqualSpacingItemDecoration.HORIZONTAL)); mRecyclerView.setAdapter(mPaginationAdapter); mSwipeRefreshLayout.setRefreshing(true); preparedListItem(); /** * add scroll listener while user reach in bottom load more will call */ mRecyclerView.addOnScrollListener(new PaginationScrollListener(mLayoutManager) { @Override protected void loadMoreItems() { isLoading = true; currentPage++; preparedListItem(); } @Override public boolean isLastPage() { return isLastPage; } @Override public boolean isLoading() { return isLoading; } }); } protected void preparedListItem() { RestApiService apiService = RetrofitInstance.getApiService(); Call<List<NewsItem>> call = apiService.getNewsFeed(currentPage); Log.d(TAG, "loadItemFromServer: " + currentPage); call.enqueue(new Callback<List<NewsItem>>() { @Override public void onResponse(Call<List<NewsItem>> call, Response<List<NewsItem>> response) { if (currentPage != PAGE_START) mPaginationAdapter.removeLoading(); mPaginationAdapter.addAll(response.body()); mSwipeRefreshLayout.setRefreshing(false); if (currentPage < totalPage) mPaginationAdapter.addLoading(); else isLastPage = true; isLoading = false; } @Override public void onFailure(Call<List<NewsItem>> call, Throwable t) { mSwipeRefreshLayout.setRefreshing(false); } }); } @Override public void onRefresh() { itemCount = 0; currentPage = PAGE_START; isLastPage = false; mPaginationAdapter.clear(); preparedListItem(); } }
After following all the above step your app is ready to use, If you have any queries, feel free to ask them in the comment section below. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development. Happy Coding 🙂
1 Comment
How to SwipeRefresh from right side.? because we need to come to first position to do SwipeRefresh from left side. I want to do SwipeRefresh from right side when it reaches the last item. Please suggest.