-
-
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, | |
); | |
}, | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
@collinjackson you should chat with @HansMuller about putting this in our samples.
need to add itemCount: _pages.length, to the PageView,builder else it does an infinite scroll on the right.
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.
// 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),
),
],
),
),
],
),
);
}
}
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),
),
],
),
),
),
],
),
);
}
}
Hi, how do I click on the PageView and make it auto load through the whole list of image widgets?
Amazing, thank you!!
It wasn't added to the samples (I assume Flutter's examples directory)?
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?
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
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
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?
I want to move to next screen on button tap instead of swipe. For this, I disabled
physics: NeverScrollableScrollPhysics
insidePageView.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.
How to rotate DOTS to VERTICAL (180º degrees) ?? Thanks!
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!
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);
It looks like this.