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

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>

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>

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"
    
    // ...
}

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>

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>

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'
    
    // ...
}

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>

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>

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>

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"

        // ...
    }
}

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'
    
    // ...
}

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>

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

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

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.