Android Native - Scroll Into Specific Position On RecyclerView

dimitrilc 1 Tallied Votes 644 Views Share

Introduction

When working with RecyclerView, sometimes we are required to scroll the RecyclerView in code, especially after addition of a list item.

In this tutorial, we will learn how to scroll to specific positions on a RecyclerView.

Goals

At the end of the tutorial, you would have learned:

  1. How to programmatically scroll on a RecyclerView.

Tools Required

  1. Android Studio. The version used in this tutorial is Android Studio Dolphin | 2021.3.1 Patch 1.

Prerequisite Knowledge

  1. Basic Android.
  2. Basic RecyclerView.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. Add these string resources into your project.

     <string name="add_first">Add First</string>
     <string name="add_last">Add Last</string>
  3. Replace the code in activity_main.xml with the code below. This adds a RecyclerView and two buttons.

     <?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=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toTopOf="@id/button_addFirst"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">
    
        </androidx.recyclerview.widget.RecyclerView>
    
        <Button
            android:id="@+id/button_addFirst"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/add_first"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/button_addLast"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent" />
    
        <Button
            android:id="@+id/button_addLast"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/add_last"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/button_addFirst" />
     </androidx.constraintlayout.widget.ConstraintLayout>
  4. Create new simple Adapter for RecyclerView using the code below.

     class MyAdapter(private val data: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
        class MyViewHolder(itemView: View) : ViewHolder(itemView){
            val textView: TextView = itemView.findViewById(R.id.textView_item)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            val itemView = LayoutInflater.from(parent.context)
                .inflate(R.layout.my_view_holder, parent,false)
    
            return MyViewHolder(itemView)
        }
    
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            holder.textView.text = data[position]
        }
    
        override fun getItemCount() = data.size
    
     }
  5. Create a file called my_view_holder.xml as a layout resource and add the code below into it.

     <?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"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/textView_item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="32sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
     </androidx.constraintlayout.widget.ConstraintLayout>
  6. Replace the code in MainActivity’s onCreate() with the code below.

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
    
        // Handle data in ViewModel in real code
        val data = ('a'..'z')
            .map { "$it" }
            .toMutableList()
    
        val myAdapter = MyAdapter(data)
    
        recyclerView.adapter = myAdapter
     }

Starter App

Our starter app contains a RecyclerView with items from a to z.

Screenshot_1668205826.png

What we will attempt to do next is to have the RecyclerView scroll to the top (first item in data list) or the bottom (last item in data list) when new items are added.

Scroll To Top

Let us try to add items to the list without any scroll logic, so that we can see what the problem is. Append the code below into onCreate().

// Add first
var counter = 0
val addFirstButton = findViewById<Button>(R.id.button_addFirst)
addFirstButton.setOnClickListener {
   data.add(0, counter++.toString())
   myAdapter.notifyItemInserted(0)
}

recycler_no_scroll.gif

As we can see in the animation above, the RecyclerView does not provide any visual feedback to the user when new items are added. This might create confusions for your users when they attempt to add new items.

To have RecyclerView scroll to the first item after it is inserted, add the observer and register it like below. This code must be added before the // Add first section of the code.

// Adds observer to scroll into position
val observer = object : AdapterDataObserver() {
   override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
       recyclerView.scrollToPosition(positionStart)
   }
}
myAdapter.registerAdapterDataObserver(observer)

Now, if we run the code, then we can see the RecyclerView scrolls to the new item that is added.

recycler_scroll_rough.gif

Fixing Blinking

Towards the end of the animation, did you catch the RecyclerView blinking, when the list is too far down? To fix that, we can switch from scrollToPosition() with smoothScrollToPosition(). This will eliminate the blinking.

// Adds observer to scroll into position
val observer = object : AdapterDataObserver() {
   override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
       recyclerView.smoothScrollToPosition(positionStart)
   }
}
myAdapter.registerAdapterDataObserver(observer)

recycler_scroll_smooth.gif

Scroll To Bottom

The observer that we created also work when we append to the end of the list as well, so we do not have to make any changes to that.

We still have to add code to bind the addLastButton button to append a new item.

// Add Last
val addLastButton = findViewById<Button>(R.id.button_addLast)
addLastButton.setOnClickListener {
   data.add(counter++.toString())
   myAdapter.notifyItemInserted(data.lastIndex)
}

recycler_scroll_smooth_bottom.gif

Summary

Congratulations, we have learned how to make RecylerView scroll into a specific position when a new item is added in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebRecyclerScrollInto.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.