Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PageView example with dots indicator
// Copyright 2017, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
/// An indicator showing the currently selected page of a PageController
class DotsIndicator extends AnimatedWidget {
DotsIndicator({
this.controller,
this.itemCount,
this.onPageSelected,
this.color: Colors.white,
}) : super(listenable: controller);
/// The PageController that this DotsIndicator is representing.
final PageController controller;
/// The number of items managed by the PageController
final int itemCount;
/// Called when a dot is tapped
final ValueChanged<int> onPageSelected;
/// The color of the dots.
///
/// Defaults to `Colors.white`.
final Color color;
// The base size of the dots
static const double _kDotSize = 8.0;
// The increase in the size of the selected dot
static const double _kMaxZoom = 2.0;
// The distance between the center of each dot
static const double _kDotSpacing = 25.0;
Widget _buildDot(int index) {
double selectedness = Curves.easeOut.transform(
max(
0.0,
1.0 - ((controller.page ?? controller.initialPage) - index).abs(),
),
);
double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness;
return new Container(
width: _kDotSpacing,
child: new Center(
child: new Material(
color: color,
type: MaterialType.circle,
child: new Container(
width: _kDotSize * zoom,
height: _kDotSize * zoom,
child: new InkWell(
onTap: () => onPageSelected(index),
),
),
),
),
);
}
Widget build(BuildContext context) {
return new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: new List<Widget>.generate(itemCount, _buildDot),
);
}
}
class MyHomePage extends StatefulWidget {
@override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
final _controller = new PageController();
static const _kDuration = const Duration(milliseconds: 300);
static const _kCurve = Curves.ease;
final _kArrowColor = Colors.black.withOpacity(0.8);
final List<Widget> _pages = <Widget>[
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlutterLogo(colors: Colors.blue),
),
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlutterLogo(style: FlutterLogoStyle.stacked, colors: Colors.red),
),
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlutterLogo(style: FlutterLogoStyle.horizontal, colors: Colors.green),
),
];
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new IconTheme(
data: new IconThemeData(color: _kArrowColor),
child: new Stack(
children: <Widget>[
new PageView.builder(
physics: new AlwaysScrollableScrollPhysics(),
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return _pages[index % _pages.length];
},
),
new Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: new Container(
color: Colors.grey[800].withOpacity(0.5),
padding: const EdgeInsets.all(20.0),
child: new Center(
child: new DotsIndicator(
controller: _controller,
itemCount: _pages.length,
onPageSelected: (int page) {
_controller.animateToPage(
page,
duration: _kDuration,
curve: _kCurve,
);
},
),
),
),
),
],
),
),
);
}
}
@collinjackson

This comment has been minimized.

Copy link
Owner Author

commented May 30, 2017

It looks like this.
screencast

@Hixie

This comment has been minimized.

Copy link

commented May 30, 2017

@collinjackson you should chat with @HansMuller about putting this in our samples.

@manujbahl

This comment has been minimized.

Copy link

commented Apr 14, 2018

need to add itemCount: _pages.length, to the PageView,builder else it does an infinite scroll on the right.

@manujbahl

This comment has been minimized.

Copy link

commented Apr 14, 2018

Also found an issue with probably with PageViewBuilder. If I add a onTap to the page content there is one situation where the tap event does not fire. I set the viewport size to 0.5 so that the right and left elements show partly. Happens when you are on page 0 and tap on page 1. In every other case Tap events are fired for any of the pages.

Below is the code I used.

@manujbahl

This comment has been minimized.

Copy link

commented Apr 14, 2018

        // Copyright 2017, the Flutter project authors.  Please see the AUTHORS file
        // for details. All rights reserved. Use of this source code is governed by a
        // BSD-style license that can be found in the LICENSE file.

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

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

        class MyApp extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return new MaterialApp(
              title: 'Flutter Demo',
              home: new MyHomePage(),
              debugShowCheckedModeBanner: false,
            );
          }
        }

        /// An indicator showing the currently selected page of a PageController
        class DotsIndicator extends AnimatedWidget {
          DotsIndicator({
            this.controller,
            this.itemCount,
            this.onPageSelected,
            this.color: Colors.white,
          }) : super(listenable: controller);

          /// The PageController that this DotsIndicator is representing.
          final PageController controller;

          /// The number of items managed by the PageController
          final int itemCount;

          /// Called when a dot is tapped
          final ValueChanged<int> onPageSelected;

          /// The color of the dots.
          ///
          /// Defaults to `Colors.white`.
          final Color color;

          // The base size of the dots
          static const double _kDotSize = 8.0;

          // The increase in the size of the selected dot
          static const double _kMaxZoom = 2.0;

          // The distance between the center of each dot
          static const double _kDotSpacing = 25.0;

          Widget _buildDot(int index) {
            double selectedness = Curves.easeOut.transform(
              max(
                0.0,
                1.0 - ((controller.page ?? controller.initialPage) - index).abs(),
              ),
            );
            double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness;
            return new Container(
              width: _kDotSpacing,
              child: new Center(
                child: new Material(
                  color: color,
                  type: MaterialType.circle,
                  child: new Container(
                    width: _kDotSize * zoom,
                    height: _kDotSize * zoom,
                    child: new InkWell(
                      onTap: () => onPageSelected(index),
                    ),
                  ),
                ),
              ),
            );
          }

          Widget build(BuildContext context) {
            return new Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: new List<Widget>.generate(itemCount, _buildDot),
            );
          }
        }

        class MyHomePage extends StatefulWidget {
          @override
          State createState() => new MyHomePageState();
        }

        class MyHomePageState extends State<MyHomePage> {

          final _controller = new PageController(viewportFraction: 0.5);

          static const _kDuration = const Duration(milliseconds: 300);

          static const _kCurve = Curves.ease;

          final _kArrowColor = Colors.black.withOpacity(0.8);

          static onTap(index) {
            print("$index selected.");
          }

          final List<Widget> _pages = <Widget>[
            new FlutterLogo(colors: Colors.blue),
            new FlutterLogo(style: FlutterLogoStyle.stacked, colors: Colors.red),
            new FlutterLogo(style: FlutterLogoStyle.horizontal, colors: Colors.green),
          ];

            Widget _buildPageItem(BuildContext context, int index) {
              return new Page(page: _pages[index], idx: index);
            }

          @override
          Widget build(BuildContext context) {
            return new Scaffold(
              body: new IconTheme(
                data: new IconThemeData(color: _kArrowColor),
                child: new Stack(
                  children: <Widget>[
                    new PageView.builder(
                      physics: new AlwaysScrollableScrollPhysics(),
                      controller: _controller,
                      itemCount: _pages.length,
                      itemBuilder: (BuildContext context, int index) {
                        return _buildPageItem(context, index % _pages.length);
                      },
                    ),
                    new Positioned(
                      bottom: 0.0,
                      left: 0.0,
                      right: 0.0,
                      child: new Container(
                        color: Colors.grey[800].withOpacity(0.5),
                        padding: const EdgeInsets.all(20.0),
                        child: new Center(
                          child: new DotsIndicator(
                            controller: _controller,
                            itemCount: _pages.length,
                            onPageSelected: (int page) {
                              _controller.animateToPage(
                                page,
                                duration: _kDuration,
                                curve: _kCurve,
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          }
        }

        class Page extends StatelessWidget {
          final page;
          final idx;

          Page({
            @required this.page,
            @required this.idx,
          });

          onTap() {
            print("${this.idx} selected.");
          }

          @override
          Widget build(BuildContext context) {
            return new Padding(
              padding: const EdgeInsets.all(8.0),
              child: new Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new Container(
                      height: 200.0,
                    child: new Card(
                      child: new Stack(
                        fit: StackFit.expand,
                        children: <Widget>[
                          this.page,
                          new Material(
                            type: MaterialType.transparency,
                            child: new InkWell(onTap: this.onTap),
                          ),
                        ],
                      ),
                    ),
                ],
              ),
            );
          }
        }
@biniama

This comment has been minimized.

Copy link

commented May 17, 2018

Bug fixed code for the ^^ above code by manujbahl

// Copyright 2017, the Flutter project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

/// An indicator showing the currently selected page of a PageController
class DotsIndicator extends AnimatedWidget {
  DotsIndicator({
    this.controller,
    this.itemCount,
    this.onPageSelected,
    this.color: Colors.white,
  }) : super(listenable: controller);

  /// The PageController that this DotsIndicator is representing.
  final PageController controller;

  /// The number of items managed by the PageController
  final int itemCount;

  /// Called when a dot is tapped
  final ValueChanged<int> onPageSelected;

  /// The color of the dots.
  ///
  /// Defaults to `Colors.white`.
  final Color color;

  // The base size of the dots
  static const double _kDotSize = 8.0;

  // The increase in the size of the selected dot
  static const double _kMaxZoom = 2.0;

  // The distance between the center of each dot
  static const double _kDotSpacing = 25.0;

  Widget _buildDot(int index) {
    double selectedness = Curves.easeOut.transform(
      max(
        0.0,
        1.0 - ((controller.page ?? controller.initialPage) - index).abs(),
      ),
    );
    double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness;
    return new Container(
      width: _kDotSpacing,
      child: new Center(
        child: new Material(
          color: color,
          type: MaterialType.circle,
          child: new Container(
            width: _kDotSize * zoom,
            height: _kDotSize * zoom,
            child: new InkWell(
              onTap: () => onPageSelected(index),
            ),
          ),
        ),
      ),
    );
  }

  Widget build(BuildContext context) {
    return new Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: new List<Widget>.generate(itemCount, _buildDot),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  final _controller = new PageController(viewportFraction: 0.5);

  static const _kDuration = const Duration(milliseconds: 300);

  static const _kCurve = Curves.ease;

  final _kArrowColor = Colors.black.withOpacity(0.8);

  static onTap(index) {
    print("$index selected.");
  }

  final List<Widget> _pages = <Widget>[
    new FlutterLogo(colors: Colors.blue),
    new FlutterLogo(style: FlutterLogoStyle.stacked, colors: Colors.red),
    new FlutterLogo(style: FlutterLogoStyle.horizontal, colors: Colors.green),
  ];

  Widget _buildPageItem(BuildContext context, int index) {
    return new Page(page: _pages[index], idx: index);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new IconTheme(
        data: new IconThemeData(color: _kArrowColor),
        child: new Stack(
          children: <Widget>[
            new PageView.builder(
              physics: new AlwaysScrollableScrollPhysics(),
              controller: _controller,
              itemCount: _pages.length,
              itemBuilder: (BuildContext context, int index) {
                return _buildPageItem(context, index % _pages.length);
              },
            ),
            new Positioned(
              bottom: 0.0,
              left: 0.0,
              right: 0.0,
              child: new Container(
                color: Colors.grey[800].withOpacity(0.5),
                padding: const EdgeInsets.all(20.0),
                child: new Center(
                  child: new DotsIndicator(
                    controller: _controller,
                    itemCount: _pages.length,
                    onPageSelected: (int page) {
                      _controller.animateToPage(
                        page,
                        duration: _kDuration,
                        curve: _kCurve,
                      );
                    },
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class Page extends StatelessWidget {
  final page;
  final idx;

  Page({
    @required this.page,
    @required this.idx,
  });

  onTap() {
    print("${this.idx} selected.");
  }

  @override
  Widget build(BuildContext context) {
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          new Container(
            height: 200.0,
            child: new Card(
              child: new Stack(
                fit: StackFit.expand,
                children: <Widget>[
                  this.page,
                  new Material(
                    type: MaterialType.transparency,
                    child: new InkWell(onTap: this.onTap),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
@tanphathuynh

This comment has been minimized.

Copy link

commented Jul 13, 2018

Hi, how do I click on the PageView and make it auto load through the whole list of image widgets?

@luke-z

This comment has been minimized.

Copy link

commented Sep 18, 2018

Amazing, thank you!!

@i-schuetz

This comment has been minimized.

Copy link

commented Mar 1, 2019

It wasn't added to the samples (I assume Flutter's examples directory)?

@sjschnuggsr

This comment has been minimized.

Copy link

commented Mar 29, 2019

Love your design and it's working great...
except when I nest it in a parent pageview I can't navigate out from first or last pages. (I can from middle pages). Makes it kind of useless in that environment. Any suggestions?

@DK15

This comment has been minimized.

Copy link

commented May 8, 2019

I want to move to next screen on button tap instead of swipe. For this, I disabled physics: NeverScrollableScrollPhysics inside PageView.builder, but this leads to dots indicator not showing up on other screens. It would be great if you could let me know how do I achieve page transition on button tap and also have dots indicator show up and highlight correct dot according to current page. @collinjackson

@DK15

This comment has been minimized.

Copy link

commented May 8, 2019

I want to move to next screen on button tap instead of swipe. For this, I disabled physics: NeverScrollableScrollPhysics inside PageView.builder, but this leads to dots indicator not showing up on other screens. It would be great if you could let me know how do I achieve page transition on button tap and also have dots indicator show up and highlight correct dot according to current page. @collinjackson

@Xihuny

This comment has been minimized.

Copy link

commented May 30, 2019

Dot indicator zoom is not working during pageview infinite scroll. Suppose, if I have 3 items in the list and when it scrolls to page 4 (which basically will show page 1) dot indicator is not working. Any workarounds?

@adrianvintu

This comment has been minimized.

Copy link

commented Jun 7, 2019

I want to move to next screen on button tap instead of swipe. For this, I disabled physics: NeverScrollableScrollPhysics inside PageView.builder, but this leads to dots indicator not showing up on other screens. It would be great if you could let me know how do I achieve page transition on button tap and also have dots indicator show up and highlight correct dot according to current page. @collinjackson

Also need an answer here.

@agueroveraalvaro

This comment has been minimized.

Copy link

commented Jun 9, 2019

How to rotate DOTS to VERTICAL (180º degrees) ?? Thanks!

@adrianvintu

This comment has been minimized.

Copy link

commented Jun 9, 2019

Is there a way to hide the dots when the keyboard is up? the dots render on top of the keyboard and it looks awful.

Thank you for your great word!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.