-
-
Save collinjackson/4fddbfa2830ea3ac033e34622f278824 to your computer and use it in GitHub Desktop.
// 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, | |
); | |
}, | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
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:
return new Container(
width: _kDotSpacing,
height: _kDotSize * _kMaxZoom,
...
)
I also played around with animating the opacity of the dots and this small change enabled that:
child: Material(
color: Color.fromRGBO(color.red, color.green, color.blue, max(selectedness, 0.5)),
...
)
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);
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!