In this tutorial, we’ll be discussing and implementing Endless Scrolling or Infinite Scroll or Load More or Pagination on RecyclerView in Android Application.
The code for the activity_main.xml layout is given below:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
tools:context=".TestKotlinActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvItems"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/list_item" />
<include
android:id="@+id/llLoader"
layout="@layout/loader"
android:layout_height="130dp"
android:layout_width="130dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The layout for the rows of the RecyclerView is defined in item_row.xml file as shown below:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:id="@+id/tvRating"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Small"/>
</LinearLayout>
The code for the ListAdapter and Movie classes is given below:
data class Movie(var title: String, var rating: Float)
class MovieAdapter(private val listItemClickListener: ListItemClickListener)
: ListAdapter<Movie, RecyclerView.ViewHolder>(ListItemCallback()) {
interface ListItemClickListener {
fun onItemClick(item: Movie, position: Int)
}
class ListItemCallback : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.title == newItem.title && oldItem.rating == newItem.rating
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
(holder as ListenItemViewHolder).bind(item, position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return ListenItemViewHolder(view)
}
inner class ListenItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvTitle: TextView = itemView.tvTitle
var tvRating: TextView = itemView.tvRating
fun bind(item: Movie, position : Int) {
tvTitle.text = item.title
tvRating.text = "Rating: ${item.rating}"
itemView.setOnClickListener {
listItemClickListener.onItemClick(item, adapterPosition)
}
}
}
}
Let’s look at the MainActivity class where we instantiate the above Adapter.
class MainActivity : AppCompatActivity(), MovieAdapter.ListItemClickListener {
val TAG = Constants.TAG;
private var movies = mutableListOf<Movie>()
private lateinit var movieAdapter: MovieAdapter
private var isLoading: Boolean = false
private lateinit var layoutManager : LinearLayoutManager
private var handler: Handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView()
}
private fun setupRecyclerView() {
movieAdapter = MovieAdapter(this)
layoutManager = LinearLayoutManager(this)
rvItems.layoutManager = layoutManager
rvItems.itemAnimator = DefaultItemAnimator()
rvItems.adapter = movieAdapter
loadData(isInitLoad = true)
rvItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!isLoading) {
if (layoutManager.findLastCompletelyVisibleItemPosition() == movies.size - 1) {
loadData()
isLoading = true
}
}
}
})
movieAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
layoutManager.scrollToPositionWithOffset(positionStart, 0)
}
})
}
fun loadData(isInitLoad: Boolean = false) {
llLoader.visibility = View.VISIBLE
if (isInitLoad) {
(0..19).mapTo(movies) { Movie("Movie $it", it.toFloat()) }
movieAdapter.submitList(movies)
llLoader.visibility = View.GONE
} else {
handler.postDelayed(Runnable {
val start = movies.size
val end = start + 10
val newMovies = ArrayList<Movie>()
(start..end).mapTo(newMovies) { Movie("Movie $it", it.toFloat()) }
updateDataList(newMovies)
isLoading = false
llLoader.visibility = View.GONE
},2000)
}
}
fun updateDataList(newList:List<Movie>){
val tempList = movies.toMutableList()
tempList.addAll(newList)
movieAdapter.submitList(tempList)
movies = tempList
}
override fun onItemClick(item: Movie, position: Int) {
Log.d(TAG, "item: $item");
}
}