How to add Scroll To Top in RecyclerView in Android using Kotlin

In this tutorial, I’m going to show you how to implement Scroll To Top in your RecyclerView in two ways.

First, by pressing the Activity’s title, and second, by pressing a button when it appears while you scroll down.

Adding Scroll To Top in Activity’s Title

First, we need to replace the ActionBar with our own Toolbar that can handle taps.

Go to your style.xml file (res > values > style.xml), and add a theme with No Action Bar.

<resources> <!-- Other themes --> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="android:actionMenuTextColor">@android:color/white</item> </style> </resources>
Code language: HTML, XML (xml)

Don’t forget to change the theme of your Activity on the AndroidManifest.xml too.

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.johncodeos.scrolltotopexample"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme.NoActionBar"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
Code language: HTML, XML (xml)

To be able to show our own Toolbar at the top of the Activity correctly, we are going to use the CoordinatorLayout.

Add the following dependency in your build.gradle if you don’t already have it.

dependencies { // ... implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" // ... }
Code language: Swift (swift)

Go to your Activity‘s layout and put your RecyclerView inside a CoordinatorLayout.

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Code language: HTML, XML (xml)

Between the CoordinatorLayout and your RecyclerView, add a RelativeLayout and set the layout behavior app:layout_behavior="@string/appbar_scrolling_view_behavior"

Without this attribute, the RecyclerView will be behind the Toolbar.

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true" tools:context=".MainActivity"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> </RelativeLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Code language: HTML, XML (xml)

If you get the error Cannot resolve symbol '@string/appbar_scrolling_view_behavior'

Go to your build.gradle of your app and add the following dependency.

dependencies { // ... implementation 'com.google.android.material:material:1.1.0' // ... }
Code language: Kotlin (kotlin)

Back in the Activity‘s layout, add an AppBarLayout above your RelativeLayout

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> </com.google.android.material.appbar.AppBarLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> </RelativeLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Code language: HTML, XML (xml)

Inside the AppBarLayout add a Toolbar

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:weightSum="1" app:contentInsetStart="0dp"> </androidx.appcompat.widget.Toolbar> </com.google.android.material.appbar.AppBarLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> </RelativeLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Code language: HTML, XML (xml)

Last, add a TextView inside the Toolbar, like that:

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:weightSum="1" app:contentInsetStart="0dp"> <TextView android:id="@+id/toolbartitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:fadingEdge="horizontal" android:focusable="true" android:focusableInTouchMode="true" android:marqueeRepeatLimit="marquee_forever" android:scrollHorizontally="true" android:textColor="#FFFFFF" android:textSize="20sp" android:textStyle="bold" /> </androidx.appcompat.widget.Toolbar> </com.google.android.material.appbar.AppBarLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> </RelativeLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Code language: HTML, XML (xml)

So at the end, will look like this:

Now, go to your Activity‘s file, customize your TextView and add a click listener to scroll to the top of the RecyclerView every time we tap the TextView.

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // ... toolbartitle.ellipsize = TextUtils.TruncateAt.MARQUEE toolbartitle.isSingleLine = true toolbartitle.isSelected = true toolbartitle.setOnClickListener { cars_rv.smoothScrollToPosition(0) } toolbartitle.text = "ScrollToTopExample" // ... } }
Code language: Kotlin (kotlin)

Done!

Adding Scroll To Top in Button

This method is very common when you create a chat. The user scrolls to see the chat history and a button with an arrow appears to scroll back.

First, add the following dependency in your build.gradle of your app, if you want to have a circular shape button(ImageView) for the Scroll To Top icon later.

dependencies { // ... implementation 'de.hdodenhof:circleimageview:3.1.0' // ... }
Code language: Kotlin (kotlin)

Then, in the layout of your Activity, add the Scroll To Top button(ImageView) outside the screen.

<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/cars_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" /> <!--It's outside the screen because is visible when the screen starts--> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/scroll_to_top_arrow" android:layout_width="45dp" android:layout_height="45dp" android:layout_alignEnd="@+id/cars_rv" android:layout_alignBottom="@+id/cars_rv" android:layout_centerInParent="true" android:layout_marginEnd="-85dp" android:layout_marginBottom="18dp" android:rotation="90" android:scaleType="centerCrop" android:src="@drawable/back_arrow" app:civ_border_width="2dp" /> </RelativeLayout>
Code language: HTML, XML (xml)

Now, in your Activity file

Add the positions for the button where do you want to be when it’s visible and hidden

Add to the button a click listener, so when we tap it, it will scroll to the top.

Also, we need to hide the arrow button so it’s not visible when the Activity starts.

class MainActivity : AppCompatActivity() { // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // ... // Scroll To Top Arrow(ImageView) Customization scroll_to_top_arrow.borderColor = Color.WHITE scroll_to_top_arrow.circleBackgroundColor = ContextCompat.getColor(this, R.color.colorPrimaryDark) scroll_to_top_arrow.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) // Positions for the arrow when is hidden and visible val whenVisibleMargin = convertDpToPixel(15f, cars_rv.context) val whenHideMargin = convertDpToPixel(-85f, cars_rv.context) // Hide the arrow at the beginning when the screen starts scroll_to_top_arrow.visibility = View.GONE // Scroll to the top when you press the arrow scroll_to_top_arrow.setOnClickListener { cars_rv.post { cars_rv.smoothScrollToPosition(0) } } // ... } private fun convertDpToPixel(dp: Float, context: Context): Float { return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) } }
Code language: Kotlin (kotlin)

Add a scroll listener in your RecyclerView to detect when we scroll to the bottom, to show the arrow.

In this example, we say that we want to show the arrow when the 12th item from the RecyclerView appears on screen while we scroll.

class MainActivity : AppCompatActivity() { // ... var animatedHide = false var animatedShow = false var findLastVisibleItemPositionValue = 12 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // ... cars_rv.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (mLayoutManager.findLastVisibleItemPosition() >= findLastVisibleItemPositionValue) { if (!animatedShow) { scroll_to_top_arrow.visibility = View.VISIBLE val params = scroll_to_top_arrow.layoutParams as RelativeLayout.LayoutParams val animator = ValueAnimator.ofInt(params.rightMargin, whenVisibleMargin.toInt()) animator.addUpdateListener { valueAnimator -> params.rightMargin = valueAnimator.animatedValue as Int scroll_to_top_arrow.requestLayout() } animator.duration = 300 animator.start() animatedShow = true animatedHide = false } } else { if (!animatedHide) { scroll_to_top_arrow.visibility = View.VISIBLE val params = scroll_to_top_arrow.layoutParams as RelativeLayout.LayoutParams val animator = ValueAnimator.ofInt(params.rightMargin, whenHideMargin.toInt()) animator.addUpdateListener { valueAnimator -> params.rightMargin = valueAnimator.animatedValue as Int scroll_to_top_arrow.requestLayout() } animator.duration = 300 animator.start() animatedHide = true animatedShow = false } } super.onScrolled(recyclerView, dx, dy) } }) } }
Code language: Kotlin (kotlin)

That’s it!

You can find the final project here

If you have any questionsplease feel free to leave a comment below

Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Taha

thanks for the tut that was very useful, i was wondering if its possible to implement the same thing but using a constraint layout instead of a relative layout ?, i tired to do it for a while but i failed to show the circular button in the screen and gave up and created a relative layout for my recycler view and circular button.