How to create an Animated Page Indicator in Flutter

A clear and attractive page indicator enhances user experience when building a mobile application with multiple pages. In this blog post, we will explore how to use the smooth_page_indicator package in Flutter to create an animated page indicator.

Flutter animated page indicator

First, create a new Flutter project if you haven’t already.

Configuration

Open your pubspec.yaml file and add the smooth_page_indicator package as a dependency:

dependencies:
  flutter:
    sdk: flutter
  smooth_page_indicator: ^1.1.0  # Check for the latest version on pub.dev

Run flutter pub get to install the package.

We have completed the configuration; now let’s delve into the Dart-side modifications..

Add Core Component

This section will focus on the core components needed to create our animated page indicator: the PageController and PageView. These components are essential for managing and displaying multiple pages that users can swipe through.

Open main.dart file and add/replace below code.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}

In this code, we define a MyApp class that returns a MaterialApp with a Home widget as its home.

Now let’s create a Home widget.

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "Animated Page Indicator Example",
          style: TextStyle(fontSize: 20),
        ),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Column(children: [])
     );
  }
}

In the above Home widget, we will define a PageController and create a PageView widget within the build method.

The PageView widget allows for horizontal or vertical swiping between different pages, making it ideal for implementing paginated layouts. It works seamlessly with the PageController, which manages which page is visible and allows for programmatic control of the page navigation. Together, they provide a robust solution for creating swipeable pages in a mobile application. The PageController can track page changes and handle animations, enhancing the user experience. These components are essential for creating multi-page interfaces in Flutter applications.

Next, let’s update the above Home widget to include the PageController and PageView widget.

class _HomeState extends State<Home> {

  final PageController _controller =
      PageController(viewportFraction: 0.8, keepPage: true);

  final List<String> _images = [
    "https://images.unsplash.com/photo-1589779677460-a15b5b5790ce?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=871&q=80",
    "https://images.unsplash.com/photo-1520301255226-bf5f144451c1?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=873&q=80",
    "https://images.unsplash.com/photo-1504472478235-9bc48ba4d60f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=871&q=80",
    "https://images.unsplash.com/photo-1535591273668-578e31182c4f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80",
    "https://images.unsplash.com/photo-1624964562918-0b8cf87deee6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80",
    "https://images.unsplash.com/photo-1597499216184-b56bf75f84c4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
  ];

  @override
  Widget build(BuildContext context) {

    final pages = List.generate(
        _images.length,
        (index) => Container(
              height: 280,
              clipBehavior: Clip.antiAlias,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(16),
              ),
              margin: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              child: Image.network(
                _images[index],
                fit: BoxFit.cover,
              ),
            ));

    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "Animated Page Indicator Example",
          style: TextStyle(fontSize: 20),
        ),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: Column(children: [

        SizedBox(
          height: 240,
          child: PageView.builder(
            controller: _controller,
            itemBuilder: (_, index) {
              return pages[index % pages.length];
            },
          ),
        )

       ])
     );
  }
}

Let’s delve into the code and understand it,

Here we define a PageController to manage page navigation and visibility within the PageView. The viewportFraction: 0.8 parameter means each page will take up 80% of the viewport, allowing the adjacent pages to be partially visible. The keepPage: true parameter ensures that the current page index is maintained when the user navigates away and returns to the PageView.

We have also defined a list of strings named images, where each string is a URL pointing to an image. This list is used to dynamically generate the pages in the PageView.

Next, we defined PageView pages using List.generate function. The List.generate function dynamically creates a list of Container widgets, each displaying an image from the provided URL in the images list. Each container has a fixed height, rounded corners (borderRadius: BorderRadius.circular(16)), and margins. The clipBehavior: Clip.antiAlias ensures smooth clipping of the child widget, which in this case is an Image.network widget displaying the image from the URL. The fit: BoxFit.cover parameter ensures the image covers the entire container.

Finally, inside the Column widget, we have created a SizedBox widget with a fixed height of 240. It is used as parent widget for PageView.  We are creating PageView using PageView.builder function. The PageView.builder creates a scrollable list of pages controlled by the previously defined PageController. The itemBuilder callback function is used to build each page, where the index represents the current page index. The expression pages[index % pages.length] ensures the pages are cyclic, meaning the list of pages repeats indefinitely.

So far, we have added a PageController and PageView for horizontal navigation and displaying pages. Now, let’s add a page indicator to visually represent the current page.

Add Animated Page Indicator

Here, we are going to use SmoothPageIndicator widget to add animated page indicator in flutter.

First, let’s insert the following import statement into the main.dart file where we intend to integrate the page indicator:

import 'package:smooth_page_indicator/smooth_page_indicator.dart';

Below the PageView widget, enclosed within a SizedBox that serves as its parent, we integrate the SmoothPageIndicator widget. This widget is synchronized with the PageController and configured with the total count of pages using the count property. Additionally, we define the desired visual effect for our page indicator. In the below example, we use the WormEffect, which creates a worm-like animation for the indicator.There are various types of effects available and you can customize these effects according to your preferences.

SmoothPageIndicator(
  controller: _controller, // PageController
  count: pages.length,
  effect: WormEffect()
)

Customizing the Indicator

SmoothPageIndicator widget offers additional properties:

axisDirection: This property determines the alignment direction of the page indicator relative to the PageView. It accepts values from the Axis enum, such as Axis.horizontal or Axis.vertical, ensuring the indicator aligns correctly with the scrolling direction of the PageView. The default value is Axis.horizontal.

The textDirection property specifies the direction for the page indicator, allowing control over whether it progresses from left to right (TextDirection.ltr) or right to left (TextDirection.rtl). It utilizes values from the TextDirection enum to define the reading order of the text within the widget. The default value is TextDirection.ltr.

The onDotClicked property of the SmoothPageIndicator widget in Flutter allows developers to define a callback function that executes when a user taps on a dot within the indicator. This feature enables custom actions or navigation based on user interaction with the page indicator, enhancing interactivity within the application.

SmoothPageIndicator(
  controller: _controller,
  count: pages.length,
  effect: WormEffect(),
  textDirection: TextDirection.rtl,
  axisDirection: Axis.vertical,
  onDotClicked: (index) {}
)

The smooth_page_indicator package offers various effects to customize your page indicator. Here are a few examples:

  1. WormEffect: The indicator looks like a worm moving between dots.
SmoothPageIndicator(
  controller: _controller,
  count: pages.length,
  effect: WormEffect(),
)
  1. JumpingDotEffect: The dot jumps to the next position.
SmoothPageIndicator(
  controller: _controller,
  count: pages.length,
  effect: JumpingDotEffect(),
)
  1. SwapEffect: The dot swaps with the next one.
SmoothPageIndicator(
  controller: _controller,
  count: pages.length,
  effect: SwapEffect(),
)
  1. ScrollingDotsEffect: The dots scroll along with the page.
SmoothPageIndicator(
  controller: _controller,
  count: pages.length,
  effect: ScrollingDotsEffect(),
)

There are several other effects supported by the SmoothPageIndicator widget; you can find a complete list on the package’s documentation page.

You can customize these effects further by passing additional parameters. For example:

SwapEffect(
  spacing: 8.0,
  radius: 4.0,
  dotWidth: 24.0,
  dotHeight: 16.0,
  paintStyle: PaintingStyle.stroke,
  strokeWidth: 1.5,
  dotColor: Colors.grey,
  activeDotColor: Colors.indigo,
  offset: 10.0,
)

Here are the details for each property available in effect widgets such as WormEffect or any other effect widget:

spacing: Defines the space between each dot in the indicator. Default value is 8.0.

radius: Sets the radius of each dot in the indicator. Default value is 4.0.

dotWidth: Specifies the width of each dot in the indicator. Default value is 16.0.

dotHeight: Specifies the height of each dot in the indicator. Default value is 16.0.

paintStyle: Determines the painting style of the dots (PaintingStyle.fill or PaintingStyle.stroke). Default value is PaintingStyle.fill.

strokeWidth: Sets the width of the stroke used when paintStyle is set to PaintingStyle.stroke. Default value is 1.0.

dotColor: Defines the color of inactive dots in the indicator. Default value is Colors.grey.

activeDotColor: Specifies the color of the active dot in the indicator. Default value is Colors.blue.

offset: Adjusts the offset of the entire indicator widget. Default value is Offset.zero.

The complete source code for this article can be found here

Conclusion

Creating an animated page indicator in Flutter involves leveraging the PageController and PageView to manage page navigation seamlessly. Integrating the SmoothPageIndicator widget from the smooth_page_indicator package enhances user experience by providing clear visual cues of the current page. Customization options such as choosing different effects, adjusting dot size, colors, and spacing allow developers to tailor the indicator to fit their app’s design and functionality perfectly. This combination ensures users can intuitively navigate content-rich applications with ease.

Leave a Reply

Your email address will not be published. Required fields are marked *

*