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"); } }