Implementing Android ViewPager with Dots Indicator: A Comprehensive Guide

Dec 06, 2025 · Programming · 8 views · 7.8

Keywords: Android | ViewPager | TabLayout | Indicator | Material Design

Abstract: This article provides a detailed exploration of creating ViewPager with bottom dots indicator in Android applications. By analyzing two distinct layout configuration approaches—nested TabLayout and separate TabLayout—combined with custom drawable selector mechanisms, it offers a complete solution from interface design to code integration. The paper thoroughly explains how to leverage the TabLayout component from the Material Design library, achieving synchronization with ViewPager through XML attributes and programmatic connections, while demonstrating how to create visually appealing indicator effects.

In Android application development, ViewPager serves as a core component for implementing swipeable page transitions, while bottom indicators significantly enhance user experience. By combining the TabLayout component, developers can create professional-grade page navigation interfaces. This article systematically introduces two primary implementation approaches and their underlying technical principles.

Two Modes of Layout Configuration

To integrate ViewPager with bottom indicators, the layout structure design must be considered first. Based on the positional relationship between TabLayout and ViewPager, two main configuration modes exist.

The first mode employs a nested layout structure, placing TabLayout directly inside ViewPager. An XML example of this configuration is:

<androidx.viewpager.widget.ViewPager
    android:id="@+id/photos_viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.tabs.TabLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</androidx.viewpager.widget.ViewPager>

The advantage of this method lies in its automatic connection mechanism—TabLayout automatically establishes association with its parent container ViewPager, requiring no additional programmatic setup. However, this configuration presents an important layout limitation: TabLayout exists as a child view of ViewPager, meaning it cannot overlay the ViewPager content but occupies separate layout space.

The second mode adopts a separated layout, allowing TabLayout and ViewPager to exist as sibling elements:

<androidx.viewpager.widget.ViewPager
    android:id="@+id/photos_viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

<com.google.android.material.tabs.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

This separated design offers greater layout flexibility, allowing developers to position TabLayout anywhere on the screen (top, bottom, or middle) according to interface requirements. Correspondingly, manual establishment of the connection between the two components is required. The core code for programmatic connection is:

ViewPager pager = (ViewPager) view.findViewById(R.id.photos_viewpager);
PagerAdapter adapter = new PhotosAdapter(getChildFragmentManager(), photosUrl);
pager.setAdapter(adapter);

TabLayout tabLayout = (TabLayout) view.findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(pager, true);

The setupWithViewPager() method here serves as the key connection point, with the second boolean parameter controlling whether to automatically update tab text. Through this approach, TabLayout can respond in real-time to ViewPager page switching events.

Implementation of Indicator Visual Effects

Creating appealing dot indicators requires carefully designed drawable resources. The core implementation involves three XML files: two defining dot shapes for different states, and one serving as a state selector.

First, define the selected state dot, typically using brighter colors to highlight the current page position:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape
            android:innerRadius="0dp"
            android:shape="ring"
            android:thickness="8dp"
            android:useLevel="false">
            <solid android:color="@color/colorAccent"/>
        </shape>    
    </item>
</layer-list>

Here, a ring shape (shape="ring") creates a hollow circle effect, with the thickness attribute controlling ring width and innerRadius set to 0 ensuring the ring draws from the center.

The default state dot uses a similar structure but with darker colors:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape
            android:innerRadius="0dp"
            android:shape="ring"
            android:thickness="8dp"
            android:useLevel="false">
            <solid android:color="@android:color/darker_gray"/>
        </shape>    
    </item>
</layer-list>

The state selector file is responsible for switching between the two dots based on TabLayout tab selection states:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/selected_dot"
          android:state_selected="true"/>

    <item android:drawable="@drawable/default_dot"/>
</selector>

This selector works by displaying the selected state dot when a tab is in selected state (state_selected="true"), and showing the default dot otherwise. The order of selector items is crucial—Android matches the first符合条件的 item from top to bottom.

Final TabLayout Configuration

After completing drawable resources, three key attributes must be added to the TabLayout XML configuration:

app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp"

The tabBackground attribute sets the previously created state selector as tab background, which is core to achieving the dot effect. tabGravity set to "center" ensures all dots align centrally horizontally, creating symmetrical visual effects. Meanwhile, tabIndicatorHeight="0dp" hides TabLayout's default underline indicator, as we have replaced it with custom dots.

The advantage of this implementation lies in its full compliance with Material Design specifications while maintaining code simplicity. By leveraging Android's state selector mechanism, we achieve visual state switching without additional Java/Kotlin code. The entire solution demonstrates how to combine standard UI components with custom drawable resources to create both aesthetically pleasing and fully functional user interface elements.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.