Android ViewPager And ViewPagerIndicator Example

A friend of mine came to me and said I want to make a Payment Transaction app. We’ve talked for a while about the project and clarify some points regarding development point of view. After some days he came up with designs, I made some changes to the designs and start developing the app. In the designs, there’s an App Intro screen with a ViewPager, a ViewPagerIndicator, and a beautiful animation when swiping through pages. I personally really like the App Intro design and thought why not write an article on it.

ViewPager With ViewPagerIndicator Example

You see the demo right..? Now, let’s start making it in Android. This is a very simple app and I did not include any kind of dependencies so we’re not gonna see the build. gradle file or Manifest file.

Android App Coding

The design is very basic first, at the top we have a ViewPager, then the ViewPagerIndicator dots, and last at the bottom there’s a Button. Below is the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:background="@drawable/intro_background"
    tools:context=".MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/myViewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/viewPagerIndicatorIconsLayout"
        android:layout_marginBottom="10dp" />


    <LinearLayout
        android:id="@+id/viewPagerIndicatorIconsLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/getStartedButton"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="20dp"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/firstDotImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@null"
            android:src="@drawable/current_position_icon" />

        <ImageView
            android:id="@+id/secondDotImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="7dp"
            android:contentDescription="@null"
            android:src="@drawable/disable_position_icon" />

        <ImageView
            android:id="@+id/thirdDotImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="7dp"
            android:contentDescription="@null"
            android:src="@drawable/disable_position_icon" />

    </LinearLayout>

    <Button
        android:id="@+id/getStartedButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="15dp"
        android:layout_marginEnd="20dp"
        android:layout_marginStart="20dp"
        android:background="@drawable/button_drawable"
        android:text="@string/get_started"
        android:textAllCaps="false"
        android:textColor="@color/colorSubText" />

</RelativeLayout>

Main Activity

class MainActivity : AppCompatActivity() {

    companion object {
        private const val MIN_SCALE = 0.65f
        private const val MIN_ALPHA = 0.3f
    }

    private lateinit var pagerAdapterView: MyPagerAdapter
    private val uiHelper = UiHelper()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
        setContentView(R.layout.activity_main)
        pagerAdapterView = MyPagerAdapter(supportFragmentManager)
        addPagerFragments()
        myViewPager.adapter = pagerAdapterView
        myViewPager.setPageTransformer(true, this::zoomOutTransformation)
        getStartedButton.typeface = uiHelper.getTypeFace(TypeFaceEnum.BUTTON_TEXT, this)
        myViewPager.addOnPageChangeListener(ViewPagerListener(this::onPageSelected))
    }

    private fun onPageSelected(position: Int) {
        when (position) {
            0 -> {
                firstDotImageView.setImageResource(R.drawable.current_position_icon)
                secondDotImageView.setImageResource(R.drawable.disable_position_icon)
                thirdDotImageView.setImageResource(R.drawable.disable_position_icon)
            }
            1 -> {
                firstDotImageView.setImageResource(R.drawable.disable_position_icon)
                secondDotImageView.setImageResource(R.drawable.current_position_icon)
                thirdDotImageView.setImageResource(R.drawable.disable_position_icon)
            }
            2 -> {
                firstDotImageView.setImageResource(R.drawable.disable_position_icon)
                secondDotImageView.setImageResource(R.drawable.disable_position_icon)
                thirdDotImageView.setImageResource(R.drawable.current_position_icon)
            }
        }
    }

    private fun addPagerFragments() {
        pagerAdapterView.addFragments(IntroFirstFragment())
        pagerAdapterView.addFragments(IntroSecondFragment())
        pagerAdapterView.addFragments(IntroThirdFragment())
    }

    private fun zoomOutTransformation(page: View, position: Float) {
        when {
            position < -1 ->
                page.alpha = 0f
            position <= 1 -> {
                page.scaleX = Math.max(MIN_SCALE, 1 - Math.abs(position))
                page.scaleY = Math.max(MIN_SCALE, 1 - Math.abs(position))
                page.alpha = Math.max(MIN_ALPHA, 1 - Math.abs(position))
            }
            else -> page.alpha = 0f
        }
    }
}

Below is the MainActivity explanation.

  • onPageSelected: This method called every time when the ViewPager changes its page position and with that, we’re changing the ViewPagerIndicator dot icons.
  • addPagerFragments: In this function, we’re adding the pages of ViewPager means all the Fragment.
  • zoomOutTransformation: This method helps the ViewPager to animate the view or zoom out when swiping through the pages.
  • onCreate: In this method, we’re setting a FULL_SCREEN flag, creating a ViewPagerAdapter, setting page transformation for zoom out, and finally adding a ViewPager page change listener.

MyPagerAdapter

class MyPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {

    private var fragmentList: MutableList<Fragment> = ArrayList()

    override fun getItem(position: Int) = fragmentList[position]

    override fun getCount() = fragmentList.size

    fun addFragments(fragment: Fragment) = fragmentList.add(fragment)
}

The PagerAdapter class is the base class providing the adapter to populates pages inside of a ViewPager. It simply adds the fragments to the collection and represents each page as a Fragment.

IntroFirstFragment

Before start making the fragment UI. I wanna show you how the first-fragment design looks like.

first_intro_fragment_design

The UI is very basic, we have an ImageView, the image title text, and a description text. Below is the intro_first_fragment.xml file.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".fragments.IntroFirstFragment">

    <ImageView
        android:id="@+id/notificationAlertsImageView"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:layout_centerInParent="true"
        android:contentDescription="@null"
        android:src="@drawable/notification_alerts_icon" />

    <TextView
        android:id="@+id/notificationAlertsTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/notificationAlertsImageView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/notification_alerts"
        android:textColor="@color/colorTitleText"
        android:textSize="45sp" />

    <TextView
        android:id="@+id/notificationAlertsSubTitleTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/notificationAlertsTextView"
        android:layout_marginEnd="20dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="25dp"
        android:gravity="center"
        android:text="@string/lorem_ipsum"
        android:textColor="@color/colorSubText"
        android:textSize="15sp" />

</RelativeLayout>

IntroFirstFragment Kotlin Class

class IntroFirstFragment : Fragment() {

    private val uiHelper = UiHelper()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = layoutInflater.inflate(R.layout.intro_first_fragment, container, false)
        view.findViewById<TextView>(R.id.notificationAlertsTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.HEADING_TYPEFACE, activity!!)
        view.findViewById<TextView>(R.id.notificationAlertsSubTitleTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.SEMI_TITLE_TYPEFACE, activity!!)
        return view
    }

}

In this fragment, we’re simply finding its views and setting Typeface to these views.

SecondIntroFragment

Below is the second-fragment design.


intro_second_fragment_design

In this fragment UI, we have the same ImageView, title, and subtitle texts. The following shows the intro_second_fragment.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".fragments.IntroSecondFragment">

    <ImageView
        android:id="@+id/easyPaymentImageView"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:layout_centerInParent="true"
        android:contentDescription="@null"
        android:src="@drawable/easy_payment_icon" />

    <TextView
        android:id="@+id/easyPaymentTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/easyPaymentImageView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/easy_payments"
        android:textColor="@color/colorTitleText"
        android:textSize="45sp" />

    <TextView
        android:id="@+id/easyPaymentSubTitleTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/easyPaymentTextView"
        android:layout_marginEnd="20dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="25dp"
        android:gravity="center"
        android:text="@string/lorem_ipsum"
        android:textColor="@color/colorSubText"
        android:textSize="15sp" />

</RelativeLayout>

SecondIntroFragment Kotlin Class

class IntroSecondFragment : Fragment() {

    private val uiHelper = UiHelper()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = layoutInflater.inflate(R.layout.intro_second_fragment, container, false)
        view.findViewById<TextView>(R.id.easyPaymentTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.HEADING_TYPEFACE, activity!!)
        view.findViewById<TextView>(R.id.easyPaymentSubTitleTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.SEMI_TITLE_TYPEFACE, activity!!)
        return view
    }
}

In here we’re doing the same work which we have done in our previous fragment like finding and setting the Typeface of TextViews.

ThirdIntroFragment

The following shows the third-fragment UI screen.

intro_third_fragment_design

Below is the intro_third_fragment.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".fragments.IntroThirdFragment">

    <ImageView
        android:id="@+id/moneyRecordImageView"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:layout_centerInParent="true"
        android:contentDescription="@null"
        android:src="@drawable/money_record_icon" />

    <TextView
        android:id="@+id/moneyRecordTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/moneyRecordImageView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/money_records"
        android:textColor="@color/colorTitleText"
        android:textSize="45sp" />

    <TextView
        android:id="@+id/moneyRecordSubTitleTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/moneyRecordTextView"
        android:layout_marginEnd="20dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="25dp"
        android:gravity="center"
        android:text="@string/lorem_ipsum"
        android:textColor="@color/colorSubText"
        android:textSize="15sp" />

</RelativeLayout>

ThirdIntroFragment Kotlin Class

class IntroThirdFragment : Fragment() {

    private val uiHelper = UiHelper()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = layoutInflater.inflate(R.layout.intro_third_fragment, container, false)
        view.findViewById<TextView>(R.id.moneyRecordTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.HEADING_TYPEFACE, activity!!)
        view.findViewById<TextView>(R.id.moneyRecordSubTitleTextView).typeface = uiHelper.getTypeFace(TypeFaceEnum.SEMI_TITLE_TYPEFACE, activity!!)
        return view
    }
}

UiHelper

class UiHelper {

    /**
     * check which type is requested as an enum parameter and return the actual typeface.
     *
     * @param typeFaceEnum enum type like maybe RisingStar_Regular.otf
     * @param activity required to create assets.
     * @return returns a typeface which is requested by as an enum parameter.
     */

    fun getTypeFace(typeFaceEnum: TypeFaceEnum, activity: Activity): Typeface {
        return Typeface.createFromAsset(activity.assets, typeFaceEnum.getName())
    }
}

This is the helper class which provides the Typeface with the requested params. The createFromAsset is the static method creates a new typeface from the specified data.

TypeFaceEnum

enum class TypeFaceEnum(val value: String) {
    HEADING_TYPEFACE("fonts/RisingStar_Regular.otf"),
    SEMI_TITLE_TYPEFACE("fonts/SourceSansPro-Regular.otf"),
    BUTTON_TEXT("fonts/Ubuntu-M.ttf");

    fun getName() = value
}

One last thing we need to complete this app is the ViewPagerListener class.

ViewPagerListener

class ViewPagerListener(private val closure: (Int) -> Unit) : ViewPager.OnPageChangeListener {

    override fun onPageScrollStateChanged(p0: Int) {
    }

    override fun onPageScrolled(p0: Int, p1: Float, p2: Int) {
    }

    override fun onPageSelected(position: Int) = closure(position)
}

This class implements the ViewPager.OnPageChangeListener class which response to changing the state of the selected page.

Alright, guys, that was all from this article. If you want the source code, images and icons of the above app get it from GitHub. If you’ve any queries regarding this article please do comment below.

Download Complete Code

Thank you for being here and keep reading…

LEAVE A REPLY

Please enter your comment!
Please enter your name here