Last active
August 17, 2023 20:06
-
-
Save collinjackson/4fddbfa2830ea3ac033e34622f278824 to your computer and use it in GitHub Desktop.
PageView example with dots indicator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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, | |
); | |
}, | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
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?
You should add itemCount to PageView.builder.
new PageView.builder( itemCount: _pages.length, physics: new AlwaysScrollableScrollPhysics(),
Thanks for the great example!
Was having null safety issues, which I fixed by adding the required modifier before the following parameters:
DotsIndicator({
required this.controller,
required this.itemCount,
required this.onPageSelected,
this.color: Colors.white,
}) : super(listenable: controller);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for the great example!
One small issue I saw was that the dots seem to move while animating. I believe they're moving because the height of the container is changing as the dots are animating smaller/larger. I solved this by setting an explicit height on the dot container:
I also played around with animating the opacity of the dots and this small change enabled that: