How to Create an Onboarding Screen in Android with ViewPager2 ?

Here is the demo of the onboarding screen which we are going to create in this article. 

We are going to learn that how we can add Onboarding Screen to our android application in the android studio so that we can provide a better user experience to the user of the application.

What is Onboarding Screen?

  • The onboarding screen can be understood as a virtual unboxing of an application. 
  • Users go through a series of screens which finally directs users to the application interface. 

Goals or purposes of Onboarding screen:

  • Welcomes user and excite them about application ahead.
  • Tell the features or functions of the application.
  • Allow users to register or log in.
  • Collect information about the interests of the user(for example – when we open the Google application for the first time it asks the user to select singers which he/she likes).

..

Let us start Onboarding Screens with ViewPager2 Android:

ViewPager2 is an improved version of the ViewPager library that offers enhanced functionality and addresses common difficulties when using ViewPager. 

..

First onboarding screen

Now create a new activity for first onboarding screen. 

And design for the first onboarding screen

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/intro_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:layout_weight="1"
        android:padding="35dp"
        tools:srcCompat="@tools:sample/backgrounds/scenic" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/intro_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@android:color/black"
        android:textStyle="bold"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        tools:text="@tools:sample/lorem" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/intro_description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Small"
        tools:text="@tools:sample/lorem/random"
        android:maxLines="3"
        android:ellipsize="end" />

</androidx.appcompat.widget.LinearLayoutCompat>

..

Now create another xml to create an item container layout for the onboarding screen

<?xml version="1.0" encoding="utf-8"?>
<!--Add ViewPage2, TabLayout & 2 MaterialButton to your layout.-->
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@android:color/white"
    android:fitsSystemWindows="true"
    android:id="@+id/dialogInfo"
    android:orientation="vertical">

    <!--ViewPager2-->
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!--indicator-->
    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@color/white"
        app:tabBackground="@drawable/intro_tab_selector"
        app:tabGravity="center"
        app:tabIndicatorHeight="0dp" />


    <!--Next-->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/btnNext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="@string/intro_next"
        android:textAllCaps="true"
        android:backgroundTint="#356DF1"
        android:textAppearance="@style/TextAppearance.AppCompat.Small"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        app:cornerRadius="30dp" />
    <!--SKIP-->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/btnSkip"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/intro_skip"
        app:cornerRadius="30dp"
        android:textAllCaps="true"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:textAppearance="@style/TextAppearance.AppCompat.Small"
        android:textColor="@android:color/darker_gray"
        android:textStyle="bold" />

</androidx.appcompat.widget.LinearLayoutCompat>

..

Next, add the functionality (in the fragment)

package com.boltuix.androidpreferences

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.boltuix.androidpreferences.AppIntro.Companion.MAX_STEP
import com.boltuix.androidpreferences.databinding.IntroAppContentBinding
import com.boltuix.androidpreferences.databinding.IntroAppDesignBinding
import com.google.android.material.tabs.TabLayoutMediator

class AppIntro : Fragment() {
    private var _binding: IntroAppContentBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        //return inflater.inflate(R.layout.intro_app_content, container, false)
        _binding = IntroAppContentBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //............................................................
        binding.viewPager2.adapter = AppIntroViewPager2Adapter()

        //............................................................
        TabLayoutMediator(binding.tabLayout, binding.viewPager2) { tab, position ->
        }.attach()

        //............................................................
        binding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)

                if(position== MAX_STEP -1){
                    binding.btnNext.text                =   getString(R.string.intro_get_started)//"Get Started"
                    binding.btnNext.contentDescription  =   getString(R.string.intro_get_started)//"Get Started"
                }else{
                    binding.btnNext.text                  = getString(R.string.intro_next)//"Next"
                    binding.btnNext.contentDescription    = getString(R.string.intro_next)//"Next"
                }
            }
        })


        //............................................................
        binding.btnSkip.setOnClickListener {
            findNavController().navigateUp()
        }

        //............................................................
        binding.btnNext.setOnClickListener {
            if(binding.btnNext.text.toString()==getString(R.string.intro_get_started)){
                findNavController().navigateUp()
            }
            else{
                // to change current page - on click "Next BUTTON"
                val current = (binding.viewPager2.currentItem) + 1
                binding.viewPager2.currentItem = current

                // to update button text
                if(current>= MAX_STEP -1){
                    binding.btnNext.text                =   getString(R.string.intro_get_started)//"Get Started"
                    binding.btnNext.contentDescription  =   getString(R.string.intro_get_started)//"Get Started"

                }else{
                    binding.btnNext.text                =   getString(R.string.intro_next)//"Next"
                    binding.btnNext.contentDescription  =   getString(R.string.intro_next)//"Next"
                }
            }
        }
    }
    companion object {
        const val MAX_STEP = 3
    }
}
//...............................................................................


//................................................................................
class AppIntroViewPager2Adapter : RecyclerView.Adapter<PagerVH2>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerVH2 {
        return PagerVH2(
            IntroAppDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    //get the size of color array
    override fun getItemCount(): Int = MAX_STEP // Int.MAX_VALUE

    //binding the screen with view
    override fun onBindViewHolder(holder: PagerVH2, position: Int) = holder.itemView.run {

        with(holder) {
            if (position == 0) {
                bindingDesign.introTitle.text = context.getString(R.string.intro_title_1)
                bindingDesign.introDescription.text = context.getString(R.string.intro_description_1)
                bindingDesign.introImage.setImageResource(R.drawable.intro_ic_a_day_at_the_park)
            }
            if (position == 1) {
                bindingDesign.introTitle.text = context.getString(R.string.intro_title_2)
                bindingDesign.introDescription.text = context.getString(R.string.intro_description_2)
                bindingDesign.introImage.setImageResource(R.drawable.intro_ic_directions)
            }
            if (position == 2) {
                bindingDesign.introTitle.text = context.getString(R.string.intro_title_3)
                bindingDesign.introDescription.text = context.getString(R.string.intro_description_3)
                bindingDesign.introImage.setImageResource(R.drawable.intro_ic_hang_out)
            }
        }
    }
}
class PagerVH2(val bindingDesign: IntroAppDesignBinding) : RecyclerView.ViewHolder(bindingDesign.root)

..

GET source code on Github:

..


Comments