image

03 Jan 2026

9K

35K

Building an Auto-Play Image Carousel Widget in Flutter

Image carousels are a ubiquitous UI component, widely used to showcase multiple images or promotional content in a compact, engaging manner. In Flutter, building such a widget is straightforward, and adding an auto-play feature further enhances its dynamic appeal. This article will guide you through creating a reusable image carousel widget with automatic scrolling functionality using Flutter's powerful widgets.

Prerequisites

  • Basic understanding of Flutter and Dart.
  • Familiarity with StatefulWidget and its lifecycle methods.

Core Components

We'll leverage a few key Flutter widgets and Dart features to construct our carousel:

  • PageView: The fundamental widget for displaying scrollable pages. It's perfect for our image carousel as it handles swiping gestures naturally.
  • PageController: Used in conjunction with PageView to programmatically control which page is displayed, allowing us to implement auto-play.
  • Timer (from dart:async): Essential for creating the auto-play mechanism, enabling us to trigger page transitions at regular intervals.
  • StatefulWidget: Necessary to manage the current page index and the state of our auto-play timer.

Step-by-Step Implementation

1. Define Your Image Data

First, let's set up a simple list of image URLs that our carousel will display. For a real application, these might come from an API or local assets.


final List<String> imageUrls = [
  'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg',
  'https://cdn.pixabay.com/photo/2018/01/12/10/19/dandelion-3077366_1280.jpg',
  'https://cdn.pixabay.com/photo/2016/11/08/05/26/tree-1807519_1280.jpg',
  'https://cdn.pixabay.com/photo/2017/02/01/22/02/mountains-2031009_1280.jpg',
];

2. Create the AutoPlayImageCarousel Widget

We'll start by defining a StatefulWidget to manage the carousel's state, including the current page index and the auto-play timer.


import 'dart:async';
import 'package:flutter/material.dart';

class AutoPlayImageCarousel extends StatefulWidget {
  final List<String> imageUrls;
  final Duration autoPlayDuration;
  final Duration transitionDuration;
  final Curve transitionCurve;

  const AutoPlayImageCarousel({
    Key? key,
    required this.imageUrls,
    this.autoPlayDuration = const Duration(seconds: 3),
    this.transitionDuration = const Duration(milliseconds: 400),
    this.transitionCurve = Curves.easeIn,
  }) : super(key: key);

  @override
  State<AutoPlayImageCarousel> createState() => _AutoPlayImageCarouselState();
}

class _AutoPlayImageCarouselState extends State<AutoPlayImageCarousel> {
  late PageController _pageController;
  late Timer _timer;
  int _currentPage = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _currentPage);
    _startAutoPlay();
  }

  @override
  void dispose() {
    _timer.cancel();
    _pageController.dispose();
    super.dispose();
  }

  // ... (rest of the code will go here)
}

3. Implement Auto-Play Logic

The auto-play functionality is managed by a Timer. In _startAutoPlay, we set up a periodic timer that increments the _currentPage and tells the _pageController to animate to the next page. We also ensure the page index wraps around when it reaches the end of the list.


  void _startAutoPlay() {
    _timer = Timer.periodic(widget.autoPlayDuration, (timer) {
      if (_pageController.hasClients) {
        int nextPage = (_currentPage + 1) % widget.imageUrls.length;
        _pageController.animateToPage(
          nextPage,
          duration: widget.transitionDuration,
          curve: widget.transitionCurve,
        );
      }
    });
  }

4. Build the UI with PageView and Image Display

Inside the build method, we'll use a PageView.builder to efficiently display our images. We'll also wrap it in a Stack to potentially add page indicators later.


  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        PageView.builder(
          controller: _pageController,
          itemCount: widget.imageUrls.length,
          onPageChanged: (index) {
            setState(() {
              _currentPage = index;
            });
          },
          itemBuilder: (context, index) {
            return Image.network(
              widget.imageUrls[index],
              fit: BoxFit.cover,
              loadingBuilder: (context, child, loadingProgress) {
                if (loadingProgress == null) return child;
                return Center(
                  child: CircularProgressIndicator(
                    value: loadingProgress.expectedTotalBytes != null
                        ? loadingProgress.cumulativeBytesLoaded /
                            loadingProgress.expectedTotalBytes!
                        : null,
                  ),
                );
              },
              errorBuilder: (context, error, stackTrace) {
                return const Center(
                  child: Icon(Icons.broken_image, size: 50, color: Colors.grey),
                );
              },
            );
          },
        ),
        // Page indicators will go here
      ],
    );
  }

5. Adding Page Indicators (Optional but Recommended)

To provide visual feedback on the current page, we can add a row of dots at the bottom of the carousel. We'll place these within the same Stack.


  Widget _buildPageIndicator() {
    return Positioned(
      bottom: 10.0,
      left: 0,
      right: 0,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: widget.imageUrls.map((url) {
          int index = widget.imageUrls.indexOf(url);
          return Container(
            width: 8.0,
            height: 8.0,
            margin: const EdgeInsets.symmetric(horizontal: 4.0),
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: _currentPage == index
                  ? Colors.white
                  : Colors.white.withOpacity(0.5),
            ),
          );
        }).toList(),
      ),
    );
  }

Then, include this indicator in the build method's Stack:


  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        PageView.builder(
          // ... (PageView builder code)
        ),
        _buildPageIndicator(), // Add the indicator here
      ],
    );
  }

Complete Code Example

Here's the full code for our AutoPlayImageCarousel widget, ready to be dropped into your Flutter project.


import 'dart:async';
import 'package:flutter/material.dart';

class AutoPlayImageCarousel extends StatefulWidget {
  final List<String> imageUrls;
  final Duration autoPlayDuration;
  final Duration transitionDuration;
  final Curve transitionCurve;
  final double height; // Added height property

  const AutoPlayImageCarousel({
    Key? key,
    required this.imageUrls,
    this.autoPlayDuration = const Duration(seconds: 3),
    this.transitionDuration = const Duration(milliseconds: 400),
    this.transitionCurve = Curves.easeIn,
    this.height = 200.0, // Default height
  }) : super(key: key);

  @override
  State<AutoPlayImageCarousel> createState() => _AutoPlayImageCarouselState();
}

class _AutoPlayImageCarouselState extends State<AutoPlayImageCarousel> {
  late PageController _pageController;
  late Timer _timer;
  int _currentPage = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _currentPage);
    _startAutoPlay();
  }

  @override
  void dispose() {
    _timer.cancel();
    _pageController.dispose();
    super.dispose();
  }

  void _startAutoPlay() {
    _timer = Timer.periodic(widget.autoPlayDuration, (timer) {
      if (_pageController.hasClients) {
        int nextPage = (_currentPage + 1) % widget.imageUrls.length;
        _pageController.animateToPage(
          nextPage,
          duration: widget.transitionDuration,
          curve: widget.transitionCurve,
        );
      }
    });
  }

  Widget _buildPageIndicator() {
    return Positioned(
      bottom: 10.0,
      left: 0,
      right: 0,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: widget.imageUrls.map((url) {
          int index = widget.imageUrls.indexOf(url);
          return Container(
            width: 8.0,
            height: 8.0,
            margin: const EdgeInsets.symmetric(horizontal: 4.0),
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: _currentPage == index
                  ? Colors.white
                  : Colors.white.withOpacity(0.5),
            ),
          );
        }).toList(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      child: Stack(
        children: [
          PageView.builder(
            controller: _pageController,
            itemCount: widget.imageUrls.length,
            onPageChanged: (index) {
              setState(() {
                _currentPage = index;
              });
            },
            itemBuilder: (context, index) {
              return Image.network(
                widget.imageUrls[index],
                fit: BoxFit.cover,
                loadingBuilder: (context, child, loadingProgress) {
                  if (loadingProgress == null) return child;
                  return Center(
                    child: CircularProgressIndicator(
                      value: loadingProgress.expectedTotalBytes != null
                          ? loadingProgress.cumulativeBytesLoaded /
                              loadingProgress.expectedTotalBytes!
                          : null,
                    ),
                  );
                },
                errorBuilder: (context, error, stackTrace) {
                  return const Center(
                    child: Icon(Icons.broken_image, size: 50, color: Colors.grey),
                  );
                },
              );
            },
          ),
          if (widget.imageUrls.isNotEmpty) _buildPageIndicator(),
        ],
      ),
    );
  }
}

Usage Example:

To use this widget in your app, simply pass your list of image URLs:


class MyHomePage extends StatelessWidget {
  final List<String> myImageUrls = [
    'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg',
    'https://cdn.pixabay.com/photo/2018/01/12/10/19/dandelion-3077366_1280.jpg',
    'https://cdn.pixabay.com/photo/2016/11/08/05/26/tree-1807519_1280.jpg',
    'https://cdn.pixabay.com/photo/2017/02/01/22/02/mountains-2031009_1280.jpg',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Auto-Play Carousel')),
      body: Center(
        child: AutoPlayImageCarousel(
          imageUrls: myImageUrls,
          height: 250.0, // Custom height
          autoPlayDuration: const Duration(seconds: 4), // Custom auto-play speed
        ),
      ),
    );
  }
}

Conclusion

You've successfully built a dynamic, auto-playing image carousel widget in Flutter! By combining PageView for scrollable content, PageController for programmatic control, and Timer for automatic transitions, we've created a versatile component. This widget is customizable with parameters for auto-play duration, transition speed, and height, making it adaptable to various UI needs.

Further enhancements could include pausing the auto-play when a user manually swipes, adding custom navigation buttons, or integrating more sophisticated page indicators from a package. However, this foundation provides a robust starting point for any auto-playing image carousel in your Flutter applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is