Last updated on: July 14, 2022
In this tutorial, I’m going to show you how to add the Load More / Infinite Scrolling feature in the RecyclerView that has a Linear, Grid or Staggered Grid Layout.
But first, let’s set up the files which are important for whatever layout you choose to implement.
Contents
Setting up Load More / Infinite Scrolling
First, let’s start by creating the layout that contains the circular progress bar we show when we load more data (progress_loading.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</LinearLayout>
Code language: HTML, XML (xml)
After that, create the ScrollListener for our RecyclerView (RecyclerViewLoadMoreScroll.kt):
class RecyclerViewLoadMoreScroll : RecyclerView.OnScrollListener {
private var visibleThreshold = 5
private lateinit var mOnLoadMoreListener: OnLoadMoreListener
private var isLoading: Boolean = false
private var lastVisibleItem: Int = 0
private var totalItemCount:Int = 0
private var mLayoutManager: RecyclerView.LayoutManager
fun setLoaded() {
isLoading = false
}
fun getLoaded(): Boolean {
return isLoading
}
fun setOnLoadMoreListener(mOnLoadMoreListener: OnLoadMoreListener) {
this.mOnLoadMoreListener = mOnLoadMoreListener
}
constructor(layoutManager:LinearLayoutManager) {
this.mLayoutManager = layoutManager
}
constructor(layoutManager:GridLayoutManager) {
this.mLayoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
constructor(layoutManager:StaggeredGridLayoutManager) {
this.mLayoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy <= 0) return
totalItemCount = mLayoutManager.itemCount
if (mLayoutManager is StaggeredGridLayoutManager) {
val lastVisibleItemPositions =
(mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
// get maximum element within the list
lastVisibleItem = getLastVisibleItem(lastVisibleItemPositions)
} else if (mLayoutManager is GridLayoutManager) {
lastVisibleItem = (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition()
} else if (mLayoutManager is LinearLayoutManager) {
lastVisibleItem = (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
if (!isLoading && totalItemCount <= lastVisibleItem + visibleThreshold) {
mOnLoadMoreListener.onLoadMore()
isLoading = true
}
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
}
Code language: Kotlin (kotlin)
In this example, we set the visibleThreshold to 5, which means the circular progress bar will show up when the user sees the 5th item from the end of our downloaded data.
Next, we have to create an interface where we are calling to load more data into our RecyclerView (OnLoadMoreListener):
interface OnLoadMoreListener {
fun onLoadMore()
}
Code language: Kotlin (kotlin)
Also, we need to create a file (if you don’t have one already in your project) with a name Constant.kt and paste the following code:
object Constant {
const val VIEW_TYPE_ITEM = 0
const val VIEW_TYPE_LOADING = 1
}
Code language: Kotlin (kotlin)
This file contains the IDs for our Item View and Loading View, so the RecyclerView can call the right view in the right moment.
Now we go to our file RecyclerViewActivity.kt where the RecyclerView lives, and we’re going to create an ArrayList called loadMoreItemsCells.
loadMoreItemsCells contains the items we load when we show the circular progress bar (Loading View).
Also, we call the methods setRVLayoutManager() and we set our RecyclerView Layout and setRVScrollListener(), where we add the ScrollListener we created before:
class RecyclerViewActivity : AppCompatActivity() {
lateinit var loadMoreItemsCells: ArrayList<String?>
lateinit var adapter: Items_RVAdapter
lateinit var scrollListener: RecyclerViewLoadMoreScroll
lateinit var mLayoutManager:RecyclerView.LayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
//** Get the data for our ArrayList
//** Set the adapter of the RecyclerView
adapter = Items_RVAdapter(itemsCells)
adapter.notifyDataSetChanged()
items_rv.adapter = adapter
//** Set the Layout Manager of the RecyclerView
setRVLayoutManager()
//** Set the scrollListerner of the RecyclerView
setRVScrollListener()
}
private fun LoadMoreData() {
//Add the Loading View
adapter.addLoadingView()
//Create the loadMoreItemsCells Arraylist
loadMoreItemsCells = ArrayList()
//Get the number of the current Items of the main Arraylist
val start = adapterLinear.itemCount
//Load 16 more items
val end = start + 16
//Use Handler if the items are loading too fast.
//If you remove it, the data will load so fast that you can't even see the LoadingView
Handler().postDelayed({
for (i in start..end) {
//Get data and add them to loadMoreItemsCells ArrayList
loadMoreItemsCells.add("Item $i")
}
//Remove the Loading View
adapterLinear.removeLoadingView()
//We adding the data to our main ArrayList
adapterLinear.addData(loadMoreItemsCells)
//Change the boolean isLoading to false
scrollListener.setLoaded()
//Update the recyclerView in the main thread
items_linear_rv.post {
adapterLinear.notifyDataSetChanged()
}
}, 3000)
}
}
Code language: Kotlin (kotlin)
But we’re not finished yet!
Now in the Recycler’s View Adapter file we’re going to add some methods that add the data we downloaded during Loading (loadMoreItemsCells) to our main ArrayList (itemsCells), and also add methods that know when to show or hide our Loading View:
class Items_RVAdapter(private var itemsCells: ArrayList<String?>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
lateinit var mcontext: Context
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class LoadingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
fun addData(dataViews: ArrayList<String?>) {
this.itemsCells.addAll(dataViews)
notifyDataSetChanged()
}
fun getItemAtPosition(position: Int): String? {
return itemsCells[position]
}
fun addLoadingView() {
//Add loading item
Handler().post {
itemsCells.add(null)
notifyItemInserted(itemsCells.size - 1)
}
}
fun removeLoadingView() {
//Remove loading item
if (itemsCells.size != 0) {
itemsCells.removeAt(itemsCells.size - 1)
notifyItemRemoved(itemsCells.size)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
mcontext = parent.context
return if (viewType == Constant.VIEW_TYPE_ITEM) {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_row, parent, false)
ItemViewHolder(view)
} else {
val view = LayoutInflater.from(mcontext).inflate(R.layout.progress_loading, parent, false)
LoadingViewHolder(view)
}
}
override fun getItemCount(): Int {
return itemsCells.size
}
override fun getItemViewType(position: Int): Int {
return if (itemsCells[position] == null) {
Constant.VIEW_TYPE_LOADING
} else {
Constant.VIEW_TYPE_ITEM
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == Constant.VIEW_TYPE_ITEM) {
holder.itemView.itemtextview.text = itemsCells[position]
}
}
}
Code language: Kotlin (kotlin)
Linear Layout
For a RecyclerView with Linear Layout we have to add the following methods in our RecyclerViewActivity.kt file:
private fun setRVLayoutManager() {
mLayoutManager = LinearLayoutManager(this)
items_rv.layoutManager = mLayoutManager
items_rv.setHasFixedSize(true)
}
private fun setRVScrollListener() {
mLayoutManager = LinearLayoutManager(this)
scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as LinearLayoutManager)
scrollListener.setOnLoadMoreListener(object : OnLoadMoreListener {
override fun onLoadMore() {
LoadMoreData()
}
})
items_rv.addOnScrollListener(scrollListener)
}
Code language: Kotlin (kotlin)
That’s it! Run the project and…
Grid Layout
A RecyclerView with Grid Layout is a little bit different than the other layouts, and we have to add the following methods into our RecyclerViewActivity.kt file:
private fun setRVLayoutManager() {
mLayoutManager = GridLayoutManager(this, 3)
items_grid_rv.layoutManager = mLayoutManager
items_grid_rv.setHasFixedSize(true)
items_grid_rv.adapter = adapterGrid
(mLayoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapterGrid.getItemViewType(position)) {
VIEW_TYPE_ITEM -> 1
VIEW_TYPE_LOADING -> 3 //number of columns of the grid
else -> -1
}
}
}
}
private fun setRVScrollListener() {
scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as GridLayoutManager)
scrollListener.setOnLoadMoreListener(object :
OnLoadMoreListener {
override fun onLoadMore() {
LoadMoreData()
}
})
items_grid_rv.addOnScrollListener(scrollListener)
}
Code language: Kotlin (kotlin)
The difference in Grid Layout from the others is that we have to set the grid columns to 1 to be the full width of the recyclerView, when we want to show the Loading View.
Let’s run the project!
Staggered Grid Layout
The Layout Manager of a RecyclerView with a Staggered Layout is the same as Linear:
private fun setRVLayoutManager() {
mLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
items_rv.layoutManager = mLayoutManager
items_rv.setHasFixedSize(true)
items_rv.adapter = adapterStaggered
}
private fun setRVScrollListener() {
scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as StaggeredGridLayoutManager)
scrollListener.setOnLoadMoreListener(object :
OnLoadMoreListener {
override fun onLoadMore() {
LoadMoreData()
}
})
items_staggered_rv.addOnScrollListener(scrollListener)
}
Code language: Kotlin (kotlin)
But it’s a little bit tricky when it comes to showing our Loading View in full width.
It doesn’t have the helper class .spanSizeLookup as the Grid Layout has. So we need to add the following code to our RecyclerView’s Adapter:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == Constant.VIEW_TYPE_ITEM) {
holder.itemView.itemtextview.text = itemsCells[position]
}else{
val layoutParams = holder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams
layoutParams.isFullSpan = true
}
}
Code language: Kotlin (kotlin)
And run it!
You can find the final project here
If you have any questions, please feel free to leave a comment below