You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/web/filipino_cuisine/lib/flutter_swiper.dart

1877 lines
50 KiB

// Package flutter_swiper:
// https://pub.dartlang.org/packages/flutter_swiper
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'flutter_page_indicator.dart';
import 'transformer_page_view.dart';
typedef SwiperOnTap = void Function(int index);
typedef SwiperDataBuilder = Widget Function(
BuildContext context, dynamic data, int index);
/// default auto play delay
const int kDefaultAutoplayDelayMs = 3000;
/// Default auto play transition duration (in millisecond)
const int kDefaultAutoplayTransactionDuration = 300;
const int kMaxValue = 2000000000;
const int kMiddleValue = 1000000000;
// ignore_for_file: constant_identifier_names
enum SwiperLayout { DEFAULT, STACK, TINDER, CUSTOM }
class Swiper extends StatefulWidget {
/// If set true , the pagination will display 'outer' of the 'content' container.
final bool outer;
/// Inner item height, this property is valid if layout=STACK or layout=TINDER or LAYOUT=CUSTOM,
final double itemHeight;
/// Inner item width, this property is valid if layout=STACK or layout=TINDER or LAYOUT=CUSTOM,
final double itemWidth;
// height of the inside container,this property is valid when outer=true,otherwise the inside container size is controlled by parent widget
final double containerHeight;
// width of the inside container,this property is valid when outer=true,otherwise the inside container size is controlled by parent widget
final double containerWidth;
/// Build item on index
final IndexedWidgetBuilder itemBuilder;
/// Support transform like Android PageView did
/// `itemBuilder` and `transformItemBuilder` must have one not null
final PageTransformer transformer;
/// count of the display items
final int itemCount;
final ValueChanged<int> onIndexChanged;
///auto play config
final bool autoplay;
///Duration of the animation between transactions (in millisecond).
final int autoplayDelay;
///disable auto play when interaction
final bool autoplayDisableOnInteraction;
///auto play transition duration (in millisecond)
final int duration;
///horizontal/vertical
final Axis scrollDirection;
///transition curve
final Curve curve;
/// Set to false to disable continuous loop mode.
final bool loop;
///Index number of initial slide.
///If not set , the `Swiper` is 'uncontrolled', which means manage index by itself
///If set , the `Swiper` is 'controlled', which means the index is fully managed by parent widget.
final int index;
///Called when tap
final SwiperOnTap onTap;
///The swiper pagination plugin
final SwiperPlugin pagination;
///the swiper control button plugin
final SwiperPlugin control;
///other plugins, you can custom your own plugin
final List<SwiperPlugin> plugins;
///
final SwiperController controller;
final ScrollPhysics physics;
///
final double viewportFraction;
/// Build in layouts
final SwiperLayout layout;
/// this value is valid when layout == SwiperLayout.CUSTOM
final CustomLayoutOption customLayoutOption;
// This value is valid when viewportFraction is set and < 1.0
final double scale;
// This value is valid when viewportFraction is set and < 1.0
final double fade;
final PageIndicatorLayout indicatorLayout;
const Swiper({
this.itemBuilder,
this.indicatorLayout = PageIndicatorLayout.NONE,
///
this.transformer,
@required this.itemCount,
this.autoplay = false,
this.layout = SwiperLayout.DEFAULT,
this.autoplayDelay = kDefaultAutoplayDelayMs,
this.autoplayDisableOnInteraction = true,
this.duration = kDefaultAutoplayTransactionDuration,
this.onIndexChanged,
this.index,
this.onTap,
this.control,
this.loop = true,
this.curve = Curves.ease,
this.scrollDirection = Axis.horizontal,
this.pagination,
this.plugins,
this.physics,
Key key,
this.controller,
this.customLayoutOption,
/// since v1.0.0
this.containerHeight,
this.containerWidth,
this.viewportFraction = 1.0,
this.itemHeight,
this.itemWidth,
this.outer = false,
this.scale,
this.fade,
}) : assert(itemBuilder != null || transformer != null,
"itemBuilder and transformItemBuilder must not be both null"),
assert(
!loop ||
((loop &&
layout == SwiperLayout.DEFAULT &&
(indicatorLayout == PageIndicatorLayout.SCALE ||
indicatorLayout == PageIndicatorLayout.COLOR ||
indicatorLayout == PageIndicatorLayout.NONE)) ||
(loop && layout != SwiperLayout.DEFAULT)),
"Only support `PageIndicatorLayout.SCALE` and `PageIndicatorLayout.COLOR`when layout==SwiperLayout.DEFAULT in loop mode"),
super(key: key);
factory Swiper.children({
@required List<Widget> children,
bool autoplay = false,
PageTransformer transformer,
int autoplayDelay = kDefaultAutoplayDelayMs,
bool reverse = false,
bool autoplayDisableOnInteraction = true,
int duration = kDefaultAutoplayTransactionDuration,
ValueChanged<int> onIndexChanged,
int index,
SwiperOnTap onTap,
bool loop = true,
Curve curve = Curves.ease,
Axis scrollDirection = Axis.horizontal,
SwiperPlugin pagination,
SwiperPlugin control,
List<SwiperPlugin> plugins,
SwiperController controller,
Key key,
CustomLayoutOption customLayoutOption,
ScrollPhysics physics,
double containerHeight,
double containerWidth,
double viewportFraction = 1.0,
double itemHeight,
double itemWidth,
bool outer = false,
double scale = 1.0,
}) {
assert(children != null, "children must not be null");
return Swiper(
transformer: transformer,
customLayoutOption: customLayoutOption,
containerHeight: containerHeight,
containerWidth: containerWidth,
viewportFraction: viewportFraction,
itemHeight: itemHeight,
itemWidth: itemWidth,
outer: outer,
scale: scale,
autoplay: autoplay,
autoplayDelay: autoplayDelay,
autoplayDisableOnInteraction: autoplayDisableOnInteraction,
duration: duration,
onIndexChanged: onIndexChanged,
index: index,
onTap: onTap,
curve: curve,
scrollDirection: scrollDirection,
pagination: pagination,
control: control,
controller: controller,
loop: loop,
plugins: plugins,
physics: physics,
key: key,
itemBuilder: (context, index) {
return children[index];
},
itemCount: children.length);
}
factory Swiper.list({
PageTransformer transformer,
List list,
CustomLayoutOption customLayoutOption,
SwiperDataBuilder builder,
bool autoplay = false,
int autoplayDelay = kDefaultAutoplayDelayMs,
bool reverse = false,
bool autoplayDisableOnInteraction = true,
int duration = kDefaultAutoplayTransactionDuration,
ValueChanged<int> onIndexChanged,
int index,
SwiperOnTap onTap,
bool loop = true,
Curve curve = Curves.ease,
Axis scrollDirection = Axis.horizontal,
SwiperPlugin pagination,
SwiperPlugin control,
List<SwiperPlugin> plugins,
SwiperController controller,
Key key,
ScrollPhysics physics,
double containerHeight,
double containerWidth,
double viewportFraction = 1.0,
double itemHeight,
double itemWidth,
bool outer = false,
double scale = 1.0,
}) {
return Swiper(
transformer: transformer,
customLayoutOption: customLayoutOption,
containerHeight: containerHeight,
containerWidth: containerWidth,
viewportFraction: viewportFraction,
itemHeight: itemHeight,
itemWidth: itemWidth,
outer: outer,
scale: scale,
autoplay: autoplay,
autoplayDelay: autoplayDelay,
autoplayDisableOnInteraction: autoplayDisableOnInteraction,
duration: duration,
onIndexChanged: onIndexChanged,
index: index,
onTap: onTap,
curve: curve,
key: key,
scrollDirection: scrollDirection,
pagination: pagination,
control: control,
controller: controller,
loop: loop,
plugins: plugins,
physics: physics,
itemBuilder: (context, index) {
return builder(context, list[index], index);
},
itemCount: list.length);
}
@override
State<StatefulWidget> createState() {
return _SwiperState();
}
}
abstract class _SwiperTimerMixin extends State<Swiper> {
Timer _timer;
SwiperController _controller;
@override
void initState() {
_controller = widget.controller;
_controller ??= SwiperController();
_controller.addListener(_onController);
_handleAutoplay();
super.initState();
}
void _onController() {
switch (_controller.event) {
case SwiperController.START_AUTOPLAY:
{
if (_timer == null) {
_startAutoplay();
}
}
break;
case SwiperController.STOP_AUTOPLAY:
{
if (_timer != null) {
_stopAutoplay();
}
}
break;
}
}
@override
void didUpdateWidget(Swiper oldWidget) {
if (_controller != oldWidget.controller) {
if (oldWidget.controller != null) {
oldWidget.controller.removeListener(_onController);
_controller = oldWidget.controller;
_controller.addListener(_onController);
}
}
_handleAutoplay();
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
if (_controller != null) {
_controller.removeListener(_onController);
// _controller.dispose();
}
_stopAutoplay();
super.dispose();
}
bool _autoplayEnabled() {
return _controller.autoplay ?? widget.autoplay;
}
void _handleAutoplay() {
if (_autoplayEnabled() && _timer != null) return;
_stopAutoplay();
if (_autoplayEnabled()) {
_startAutoplay();
}
}
void _startAutoplay() {
assert(_timer == null, "Timer must be stopped before start!");
_timer =
Timer.periodic(Duration(milliseconds: widget.autoplayDelay), _onTimer);
}
void _onTimer(Timer timer) {
_controller.next(animation: true);
}
void _stopAutoplay() {
if (_timer != null) {
_timer.cancel();
_timer = null;
}
}
}
class _SwiperState extends _SwiperTimerMixin {
int _activeIndex;
TransformerPageController _pageController;
Widget _wrapTap(BuildContext context, int index) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
widget.onTap(index);
},
child: widget.itemBuilder(context, index),
);
}
@override
void initState() {
_activeIndex = widget.index ?? 0;
if (_isPageViewLayout()) {
_pageController = TransformerPageController(
initialPage: widget.index,
loop: widget.loop,
itemCount: widget.itemCount,
reverse:
widget.transformer == null ? false : widget.transformer.reverse,
viewportFraction: widget.viewportFraction);
}
super.initState();
}
bool _isPageViewLayout() {
return widget.layout == null || widget.layout == SwiperLayout.DEFAULT;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
bool _getReverse(Swiper widget) =>
widget.transformer == null ? false : widget.transformer.reverse;
@override
void didUpdateWidget(Swiper oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isPageViewLayout()) {
if (_pageController == null ||
(widget.index != oldWidget.index ||
widget.loop != oldWidget.loop ||
widget.itemCount != oldWidget.itemCount ||
widget.viewportFraction != oldWidget.viewportFraction ||
_getReverse(widget) != _getReverse(oldWidget))) {
_pageController = TransformerPageController(
initialPage: widget.index,
loop: widget.loop,
itemCount: widget.itemCount,
reverse: _getReverse(widget),
viewportFraction: widget.viewportFraction);
}
} else {
scheduleMicrotask(() {
// So that we have a chance to do `removeListener` in child widgets.
if (_pageController != null) {
_pageController.dispose();
_pageController = null;
}
});
}
if (widget.index != null && widget.index != _activeIndex) {
_activeIndex = widget.index;
}
}
void _onIndexChanged(int index) {
setState(() {
_activeIndex = index;
});
if (widget.onIndexChanged != null) {
widget.onIndexChanged(index);
}
}
Widget _buildSwiper() {
IndexedWidgetBuilder itemBuilder;
if (widget.onTap != null) {
itemBuilder = _wrapTap;
} else {
itemBuilder = widget.itemBuilder;
}
if (widget.layout == SwiperLayout.STACK) {
return _StackSwiper(
loop: widget.loop,
itemWidth: widget.itemWidth,
itemHeight: widget.itemHeight,
itemCount: widget.itemCount,
itemBuilder: itemBuilder,
index: _activeIndex,
curve: widget.curve,
duration: widget.duration,
onIndexChanged: _onIndexChanged,
controller: _controller,
scrollDirection: widget.scrollDirection,
);
} else if (_isPageViewLayout()) {
PageTransformer transformer = widget.transformer;
if (widget.scale != null || widget.fade != null) {
transformer =
ScaleAndFadeTransformer(scale: widget.scale, fade: widget.fade);
}
Widget child = TransformerPageView(
pageController: _pageController,
loop: widget.loop,
itemCount: widget.itemCount,
itemBuilder: itemBuilder,
transformer: transformer,
viewportFraction: widget.viewportFraction,
index: _activeIndex,
duration: Duration(milliseconds: widget.duration),
scrollDirection: widget.scrollDirection,
onPageChanged: _onIndexChanged,
curve: widget.curve,
physics: widget.physics,
controller: _controller,
);
if (widget.autoplayDisableOnInteraction && widget.autoplay) {
return NotificationListener(
child: child,
onNotification: (dynamic notification) {
if (notification is ScrollStartNotification) {
if (notification.dragDetails != null) {
//by human
if (_timer != null) _stopAutoplay();
}
} else if (notification is ScrollEndNotification) {
if (_timer == null) _startAutoplay();
}
return false;
},
);
}
return child;
} else if (widget.layout == SwiperLayout.TINDER) {
return _TinderSwiper(
loop: widget.loop,
itemWidth: widget.itemWidth,
itemHeight: widget.itemHeight,
itemCount: widget.itemCount,
itemBuilder: itemBuilder,
index: _activeIndex,
curve: widget.curve,
duration: widget.duration,
onIndexChanged: _onIndexChanged,
controller: _controller,
scrollDirection: widget.scrollDirection,
);
} else if (widget.layout == SwiperLayout.CUSTOM) {
return _CustomLayoutSwiper(
loop: widget.loop,
option: widget.customLayoutOption,
itemWidth: widget.itemWidth,
itemHeight: widget.itemHeight,
itemCount: widget.itemCount,
itemBuilder: itemBuilder,
index: _activeIndex,
curve: widget.curve,
duration: widget.duration,
onIndexChanged: _onIndexChanged,
controller: _controller,
scrollDirection: widget.scrollDirection,
);
} else {
return Container();
}
}
SwiperPluginConfig _ensureConfig(SwiperPluginConfig config) {
config ??= SwiperPluginConfig(
outer: widget.outer,
itemCount: widget.itemCount,
layout: widget.layout,
indicatorLayout: widget.indicatorLayout,
pageController: _pageController,
activeIndex: _activeIndex,
scrollDirection: widget.scrollDirection,
controller: _controller,
loop: widget.loop);
return config;
}
List<Widget> _ensureListForStack(
Widget swiper, List<Widget> listForStack, Widget widget) {
if (listForStack == null) {
listForStack = [swiper, widget];
} else {
listForStack.add(widget);
}
return listForStack;
}
@override
Widget build(BuildContext context) {
Widget swiper = _buildSwiper();
List<Widget> listForStack;
SwiperPluginConfig config;
if (widget.control != null) {
//Stack
config = _ensureConfig(config);
listForStack = _ensureListForStack(
swiper, listForStack, widget.control.build(context, config));
}
if (widget.plugins != null) {
config = _ensureConfig(config);
for (SwiperPlugin plugin in widget.plugins) {
listForStack = _ensureListForStack(
swiper, listForStack, plugin.build(context, config));
}
}
if (widget.pagination != null) {
config = _ensureConfig(config);
if (widget.outer) {
return _buildOuterPagination(
widget.pagination as SwiperPagination,
listForStack == null ? swiper : Stack(children: listForStack),
config);
} else {
listForStack = _ensureListForStack(
swiper, listForStack, widget.pagination.build(context, config));
}
}
if (listForStack != null) {
return Stack(
children: listForStack,
);
}
return swiper;
}
Widget _buildOuterPagination(
SwiperPagination pagination, Widget swiper, SwiperPluginConfig config) {
List<Widget> list = [];
//Only support bottom yet!
if (widget.containerHeight != null || widget.containerWidth != null) {
list.add(swiper);
} else {
list.add(Expanded(child: swiper));
}
list.add(Align(
alignment: Alignment.center,
child: pagination.build(context, config),
));
return Column(
children: list,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
);
}
}
abstract class _SubSwiper extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
final int itemCount;
final int index;
final ValueChanged<int> onIndexChanged;
final SwiperController controller;
final int duration;
final Curve curve;
final double itemWidth;
final double itemHeight;
final bool loop;
final Axis scrollDirection;
const _SubSwiper(
{Key key,
this.loop,
this.itemHeight,
this.itemWidth,
this.duration,
this.curve,
this.itemBuilder,
this.controller,
this.index,
this.itemCount,
this.scrollDirection = Axis.horizontal,
this.onIndexChanged})
: super(key: key);
int getCorrectIndex(int indexNeedsFix) {
if (itemCount == 0) return 0;
int value = indexNeedsFix % itemCount;
if (value < 0) {
value += itemCount;
}
return value;
}
}
class _TinderSwiper extends _SubSwiper {
const _TinderSwiper({
Key key,
Curve curve,
int duration,
SwiperController controller,
ValueChanged<int> onIndexChanged,
@required double itemHeight,
@required double itemWidth,
IndexedWidgetBuilder itemBuilder,
int index,
bool loop,
int itemCount,
Axis scrollDirection,
}) : assert(itemWidth != null && itemHeight != null),
super(
loop: loop,
key: key,
itemWidth: itemWidth,
itemHeight: itemHeight,
itemBuilder: itemBuilder,
curve: curve,
duration: duration,
controller: controller,
index: index,
onIndexChanged: onIndexChanged,
itemCount: itemCount,
scrollDirection: scrollDirection);
@override
State<StatefulWidget> createState() {
return _TinderState();
}
}
class _StackSwiper extends _SubSwiper {
const _StackSwiper({
Key key,
Curve curve,
int duration,
SwiperController controller,
ValueChanged<int> onIndexChanged,
double itemHeight,
double itemWidth,
IndexedWidgetBuilder itemBuilder,
int index,
bool loop,
int itemCount,
Axis scrollDirection,
}) : super(
loop: loop,
key: key,
itemWidth: itemWidth,
itemHeight: itemHeight,
itemBuilder: itemBuilder,
curve: curve,
duration: duration,
controller: controller,
index: index,
onIndexChanged: onIndexChanged,
itemCount: itemCount,
scrollDirection: scrollDirection);
@override
State<StatefulWidget> createState() {
return _StackViewState();
}
}
class _TinderState extends _CustomLayoutStateBase<_TinderSwiper> {
List<double> scales;
List<double> offsetsX;
List<double> offsetsY;
List<double> opacity;
List<double> rotates;
double getOffsetY(double scale) {
return widget.itemHeight - widget.itemHeight * scale;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
void didUpdateWidget(_TinderSwiper oldWidget) {
_updateValues();
super.didUpdateWidget(oldWidget);
}
@override
void afterRender() {
super.afterRender();
_startIndex = -3;
_animationCount = 5;
opacity = [0.0, 0.9, 0.9, 1.0, 0.0, 0.0];
scales = [0.80, 0.80, 0.85, 0.90, 1.0, 1.0, 1.0];
rotates = [0.0, 0.0, 0.0, 0.0, 20.0, 25.0];
_updateValues();
}
void _updateValues() {
if (widget.scrollDirection == Axis.horizontal) {
offsetsX = [0.0, 0.0, 0.0, 0.0, _swiperWidth, _swiperWidth];
offsetsY = [
0.0,
0.0,
-5.0,
-10.0,
-15.0,
-20.0,
];
} else {
offsetsX = [
0.0,
0.0,
5.0,
10.0,
15.0,
20.0,
];
offsetsY = [0.0, 0.0, 0.0, 0.0, _swiperHeight, _swiperHeight];
}
}
@override
Widget _buildItem(int i, int realIndex, double animationValue) {
double s = _getValue(scales, animationValue, i);
double f = _getValue(offsetsX, animationValue, i);
double fy = _getValue(offsetsY, animationValue, i);
double o = _getValue(opacity, animationValue, i);
double a = _getValue(rotates, animationValue, i);
Alignment alignment = widget.scrollDirection == Axis.horizontal
? Alignment.bottomCenter
: Alignment.centerLeft;
return Opacity(
opacity: o,
child: Transform.rotate(
angle: a / 180.0,
child: Transform.translate(
key: ValueKey<int>(_currentIndex + i),
offset: Offset(f, fy),
child: Transform.scale(
scale: s,
alignment: alignment,
child: SizedBox(
width: widget.itemWidth ?? double.infinity,
height: widget.itemHeight ?? double.infinity,
child: widget.itemBuilder(context, realIndex),
),
),
),
),
);
}
}
class _StackViewState extends _CustomLayoutStateBase<_StackSwiper> {
List<double> scales;
List<double> offsets;
List<double> opacity;
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
void _updateValues() {
if (widget.scrollDirection == Axis.horizontal) {
double space = (_swiperWidth - widget.itemWidth) / 2;
offsets = [-space, -space / 3 * 2, -space / 3, 0.0, _swiperWidth];
} else {
double space = (_swiperHeight - widget.itemHeight) / 2;
offsets = [-space, -space / 3 * 2, -space / 3, 0.0, _swiperHeight];
}
}
@override
void didUpdateWidget(_StackSwiper oldWidget) {
_updateValues();
super.didUpdateWidget(oldWidget);
}
@override
void afterRender() {
super.afterRender();
//length of the values array below
_animationCount = 5;
//Array below this line, '0' index is 1.0 ,witch is the first item show in swiper.
_startIndex = -3;
scales = [0.7, 0.8, 0.9, 1.0, 1.0];
opacity = [0.0, 0.5, 1.0, 1.0, 1.0];
_updateValues();
}
@override
Widget _buildItem(int i, int realIndex, double animationValue) {
double s = _getValue(scales, animationValue, i);
double f = _getValue(offsets, animationValue, i);
double o = _getValue(opacity, animationValue, i);
Offset offset = widget.scrollDirection == Axis.horizontal
? Offset(f, 0.0)
: Offset(0.0, f);
Alignment alignment = widget.scrollDirection == Axis.horizontal
? Alignment.centerLeft
: Alignment.topCenter;
return Opacity(
opacity: o,
child: Transform.translate(
key: ValueKey<int>(_currentIndex + i),
offset: offset,
child: Transform.scale(
scale: s,
alignment: alignment,
child: SizedBox(
width: widget.itemWidth ?? double.infinity,
height: widget.itemHeight ?? double.infinity,
child: widget.itemBuilder(context, realIndex),
),
),
),
);
}
}
class ScaleAndFadeTransformer extends PageTransformer {
final double _scale;
final double _fade;
ScaleAndFadeTransformer({double fade = 0.3, double scale = 0.8})
: _fade = fade,
_scale = scale;
@override
// ignore: avoid_renaming_method_parameters
Widget transform(Widget item, TransformInfo info) {
double position = info.position;
Widget child = item;
if (_scale != null) {
double scaleFactor = (1 - position.abs()) * (1 - _scale);
double scale = _scale + scaleFactor;
child = Transform.scale(
scale: scale,
child: item,
);
}
if (_fade != null) {
double fadeFactor = (1 - position.abs()) * (1 - _fade);
double opacity = _fade + fadeFactor;
child = Opacity(
opacity: opacity,
child: child,
);
}
return child;
}
}
class SwiperControl extends SwiperPlugin {
///IconData for previous
final IconData iconPrevious;
///iconData fopr next
final IconData iconNext;
///icon size
final double size;
///Icon normal color, The theme's [ThemeData.primaryColor] by default.
final Color color;
///if set loop=false on Swiper, this color will be used when swiper goto the last slide.
///The theme's [ThemeData.disabledColor] by default.
final Color disableColor;
final EdgeInsetsGeometry padding;
final Key key;
const SwiperControl(
{this.iconPrevious = Icons.arrow_back_ios,
this.iconNext = Icons.arrow_forward_ios,
this.color,
this.disableColor,
this.key,
this.size = 30.0,
this.padding = const EdgeInsets.all(5.0)});
Widget buildButton(SwiperPluginConfig config, Color color, IconData iconDaga,
int quarterTurns, bool previous) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (previous) {
config.controller.previous(animation: true);
} else {
config.controller.next(animation: true);
}
},
child: Padding(
padding: padding,
child: RotatedBox(
quarterTurns: quarterTurns,
child: Icon(
iconDaga,
semanticLabel: previous ? "Previous" : "Next",
size: size,
color: color,
))),
);
}
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
ThemeData themeData = Theme.of(context);
Color color = this.color ?? themeData.primaryColor;
Color disableColor = this.disableColor ?? themeData.disabledColor;
Color prevColor;
Color nextColor;
if (config.loop) {
prevColor = nextColor = color;
} else {
bool next = config.activeIndex < config.itemCount - 1;
bool prev = config.activeIndex > 0;
prevColor = prev ? color : disableColor;
nextColor = next ? color : disableColor;
}
Widget child;
if (config.scrollDirection == Axis.horizontal) {
child = Row(
key: key,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
buildButton(config, prevColor, iconPrevious, 0, true),
buildButton(config, nextColor, iconNext, 0, false)
],
);
} else {
child = Column(
key: key,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
buildButton(config, prevColor, iconPrevious, -3, true),
buildButton(config, nextColor, iconNext, -3, false)
],
);
}
return SizedBox(
height: double.infinity,
child: child,
width: double.infinity,
);
}
}
class SwiperController extends IndexController {
// Autoplay is started
static const int START_AUTOPLAY = 2;
// Autoplay is stopped.
static const int STOP_AUTOPLAY = 3;
// Indicate that the user is swiping
static const int SWIPE = 4;
// Indicate that the `Swiper` has changed it's index and is building it's ui ,so that the
// `SwiperPluginConfig` is available.
static const int BUILD = 5;
// available when `event` == SwiperController.BUILD
SwiperPluginConfig config;
// available when `event` == SwiperController.SWIPE
// this value is PageViewController.pos
double pos;
// ignore: overridden_fields, annotate_overrides
int index;
// ignore: overridden_fields, annotate_overrides
bool animation;
bool autoplay;
SwiperController();
void startAutoplay() {
event = SwiperController.START_AUTOPLAY;
autoplay = true;
notifyListeners();
}
void stopAutoplay() {
event = SwiperController.STOP_AUTOPLAY;
autoplay = false;
notifyListeners();
}
}
class FractionPaginationBuilder extends SwiperPlugin {
///color ,if set null , will be Theme.of(context).scaffoldBackgroundColor
final Color color;
///color when active,if set null , will be Theme.of(context).primaryColor
final Color activeColor;
////font size
final double fontSize;
///font size when active
final double activeFontSize;
final Key key;
const FractionPaginationBuilder(
{this.color,
this.fontSize = 20.0,
this.key,
this.activeColor,
this.activeFontSize = 35.0});
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
ThemeData themeData = Theme.of(context);
Color activeColor = this.activeColor ?? themeData.primaryColor;
Color color = this.color ?? themeData.scaffoldBackgroundColor;
if (Axis.vertical == config.scrollDirection) {
return Column(
key: key,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${config.activeIndex + 1}",
style: TextStyle(color: activeColor, fontSize: activeFontSize),
),
Text(
"/",
style: TextStyle(color: color, fontSize: fontSize),
),
Text(
"${config.itemCount}",
style: TextStyle(color: color, fontSize: fontSize),
)
],
);
} else {
return Row(
key: key,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
"${config.activeIndex + 1}",
style: TextStyle(color: activeColor, fontSize: activeFontSize),
),
Text(
" / ${config.itemCount}",
style: TextStyle(color: color, fontSize: fontSize),
)
],
);
}
}
}
class RectSwiperPaginationBuilder extends SwiperPlugin {
///color when current index,if set null , will be Theme.of(context).primaryColor
final Color activeColor;
///,if set null , will be Theme.of(context).scaffoldBackgroundColor
final Color color;
///Size of the rect when activate
final Size activeSize;
///Size of the rect
final Size size;
/// Space between rects
final double space;
final Key key;
const RectSwiperPaginationBuilder(
{this.activeColor,
this.color,
this.key,
this.size = const Size(10.0, 2.0),
this.activeSize = const Size(10.0, 2.0),
this.space = 3.0});
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
ThemeData themeData = Theme.of(context);
Color activeColor = this.activeColor ?? themeData.primaryColor;
Color color = this.color ?? themeData.scaffoldBackgroundColor;
List<Widget> list = [];
if (config.itemCount > 20) {
// ignore: avoid_print
print(
"The itemCount is too big, we suggest use FractionPaginationBuilder instead of DotSwiperPaginationBuilder in this sitituation");
}
int itemCount = config.itemCount;
int activeIndex = config.activeIndex;
for (int i = 0; i < itemCount; ++i) {
bool active = i == activeIndex;
Size size = active ? activeSize : this.size;
list.add(SizedBox(
width: size.width,
height: size.height,
child: Container(
color: active ? activeColor : color,
key: Key("pagination_$i"),
margin: EdgeInsets.all(space),
),
));
}
if (config.scrollDirection == Axis.vertical) {
return Column(
key: key,
mainAxisSize: MainAxisSize.min,
children: list,
);
} else {
return Row(
key: key,
mainAxisSize: MainAxisSize.min,
children: list,
);
}
}
}
class DotSwiperPaginationBuilder extends SwiperPlugin {
///color when current index,if set null , will be Theme.of(context).primaryColor
final Color activeColor;
///,if set null , will be Theme.of(context).scaffoldBackgroundColor
final Color color;
///Size of the dot when activate
final double activeSize;
///Size of the dot
final double size;
/// Space between dots
final double space;
final Key key;
const DotSwiperPaginationBuilder(
{this.activeColor,
this.color,
this.key,
this.size = 10.0,
this.activeSize = 10.0,
this.space = 3.0});
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
if (config.itemCount > 20) {
// ignore: avoid_print
print(
"The itemCount is too big, we suggest use FractionPaginationBuilder instead of DotSwiperPaginationBuilder in this sitituation");
}
Color activeColor = this.activeColor;
Color color = this.color;
if (activeColor == null || color == null) {
ThemeData themeData = Theme.of(context);
activeColor = this.activeColor ?? themeData.primaryColor;
color = this.color ?? themeData.scaffoldBackgroundColor;
}
if (config.indicatorLayout != PageIndicatorLayout.NONE &&
config.layout == SwiperLayout.DEFAULT) {
return PageIndicator(
count: config.itemCount,
controller: config.pageController,
layout: config.indicatorLayout,
size: size,
activeColor: activeColor,
color: color,
space: space,
);
}
List<Widget> list = [];
int itemCount = config.itemCount;
int activeIndex = config.activeIndex;
for (int i = 0; i < itemCount; ++i) {
bool active = i == activeIndex;
list.add(Container(
key: Key("pagination_$i"),
margin: EdgeInsets.all(space),
child: ClipOval(
child: Container(
color: active ? activeColor : color,
width: active ? activeSize : size,
height: active ? activeSize : size,
),
),
));
}
if (config.scrollDirection == Axis.vertical) {
return Column(
key: key,
mainAxisSize: MainAxisSize.min,
children: list,
);
} else {
return Row(
key: key,
mainAxisSize: MainAxisSize.min,
children: list,
);
}
}
}
typedef SwiperPaginationBuilder = Widget Function(
BuildContext context, SwiperPluginConfig config);
class SwiperCustomPagination extends SwiperPlugin {
final SwiperPaginationBuilder builder;
SwiperCustomPagination({@required this.builder}) : assert(builder != null);
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
return builder(context, config);
}
}
class SwiperPagination extends SwiperPlugin {
/// dot style pagination
static const SwiperPlugin dots = DotSwiperPaginationBuilder();
/// fraction style pagination
static const SwiperPlugin fraction = FractionPaginationBuilder();
static const SwiperPlugin rect = RectSwiperPaginationBuilder();
/// Alignment.bottomCenter by default when scrollDirection== Axis.horizontal
/// Alignment.centerRight by default when scrollDirection== Axis.vertical
final Alignment alignment;
/// Distance between pagination and the container
final EdgeInsetsGeometry margin;
/// Build the widet
final SwiperPlugin builder;
final Key key;
const SwiperPagination(
{this.alignment,
this.key,
this.margin = const EdgeInsets.all(10.0),
this.builder = SwiperPagination.dots});
@override
Widget build(BuildContext context, SwiperPluginConfig config) {
Alignment alignment = this.alignment ??
(config.scrollDirection == Axis.horizontal
? Alignment.bottomCenter
: Alignment.centerRight);
Widget child = Container(
margin: margin,
child: builder.build(context, config),
);
if (!config.outer) {
child = Align(
key: key,
alignment: alignment,
child: child,
);
}
return child;
}
}
/// plugin to display swiper components
///
abstract class SwiperPlugin {
const SwiperPlugin();
Widget build(BuildContext context, SwiperPluginConfig config);
}
class SwiperPluginConfig {
final int activeIndex;
final int itemCount;
final PageIndicatorLayout indicatorLayout;
final Axis scrollDirection;
final bool loop;
final bool outer;
final PageController pageController;
final SwiperController controller;
final SwiperLayout layout;
const SwiperPluginConfig(
{this.activeIndex,
this.itemCount,
this.indicatorLayout,
this.outer,
@required this.scrollDirection,
@required this.controller,
this.pageController,
this.layout,
this.loop})
: assert(scrollDirection != null),
assert(controller != null);
}
class SwiperPluginView extends StatelessWidget {
final SwiperPlugin plugin;
final SwiperPluginConfig config;
const SwiperPluginView(this.plugin, this.config);
@override
Widget build(BuildContext context) {
return plugin.build(context, config);
}
}
abstract class _CustomLayoutStateBase<T extends _SubSwiper> extends State<T>
with SingleTickerProviderStateMixin {
double _swiperWidth;
double _swiperHeight;
Animation<double> _animation;
AnimationController _animationController;
int _startIndex;
int _animationCount;
@override
void initState() {
if (widget.itemWidth == null) {
throw Exception(
"==============\n\nwidget.itemWith must not be null when use stack layout.\n========\n");
}
_createAnimationController();
widget.controller.addListener(_onController);
super.initState();
}
void _createAnimationController() {
_animationController = AnimationController(vsync: this, value: 0.5);
Tween<double> tween = Tween(begin: 0.0, end: 1.0);
_animation = tween.animate(_animationController);
}
@override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback(_getSize);
super.didChangeDependencies();
}
void _getSize(dynamic _) {
afterRender();
}
@mustCallSuper
void afterRender() {
RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;
_swiperWidth = size.width;
_swiperHeight = size.height;
setState(() {});
}
@override
void didUpdateWidget(T oldWidget) {
if (widget.controller != oldWidget.controller) {
oldWidget.controller.removeListener(_onController);
widget.controller.addListener(_onController);
}
if (widget.loop != oldWidget.loop) {
if (!widget.loop) {
_currentIndex = _ensureIndex(_currentIndex);
}
}
super.didUpdateWidget(oldWidget);
}
int _ensureIndex(int index) {
index = index % widget.itemCount;
if (index < 0) {
index += widget.itemCount;
}
return index;
}
@override
void dispose() {
widget.controller.removeListener(_onController);
_animationController?.dispose();
super.dispose();
}
Widget _buildItem(int i, int realIndex, double animationValue);
Widget _buildContainer(List<Widget> list) {
return Stack(
children: list,
);
}
Widget _buildAnimation(BuildContext context, Widget w) {
List<Widget> list = [];
double animationValue = _animation.value;
for (int i = 0; i < _animationCount; ++i) {
int realIndex = _currentIndex + i + _startIndex;
realIndex = realIndex % widget.itemCount;
if (realIndex < 0) {
realIndex += widget.itemCount;
}
list.add(_buildItem(i, realIndex, animationValue));
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: _onPanStart,
onPanEnd: _onPanEnd,
onPanUpdate: _onPanUpdate,
child: ClipRect(
child: Center(
child: _buildContainer(list),
),
),
);
}
@override
Widget build(BuildContext context) {
if (_animationCount == null) {
return Container();
}
return AnimatedBuilder(
animation: _animationController, builder: _buildAnimation);
}
double _currentValue;
double _currentPos;
bool _lockScroll = false;
Future<void> _move(double position, {int nextIndex}) async {
if (_lockScroll) return;
try {
_lockScroll = true;
await _animationController.animateTo(position,
duration: Duration(milliseconds: widget.duration),
curve: widget.curve);
if (nextIndex != null) {
widget.onIndexChanged(widget.getCorrectIndex(nextIndex));
}
} catch (e) {
// ignore: avoid_print
print(e);
} finally {
if (nextIndex != null) {
try {
_animationController.value = 0.5;
} catch (e) {
// ignore: avoid_print
print(e);
}
_currentIndex = nextIndex;
}
_lockScroll = false;
}
}
int _nextIndex() {
int index = _currentIndex + 1;
if (!widget.loop && index >= widget.itemCount - 1) {
return widget.itemCount - 1;
}
return index;
}
int _prevIndex() {
int index = _currentIndex - 1;
if (!widget.loop && index < 0) {
return 0;
}
return index;
}
void _onController() {
switch (widget.controller.event) {
case IndexController.PREVIOUS:
int prevIndex = _prevIndex();
if (prevIndex == _currentIndex) return;
_move(1.0, nextIndex: prevIndex);
break;
case IndexController.NEXT:
int nextIndex = _nextIndex();
if (nextIndex == _currentIndex) return;
_move(0.0, nextIndex: nextIndex);
break;
case IndexController.MOVE:
throw Exception(
"Custom layout does not support SwiperControllerEvent.MOVE_INDEX yet!");
case SwiperController.STOP_AUTOPLAY:
case SwiperController.START_AUTOPLAY:
break;
}
}
void _onPanEnd(DragEndDetails details) {
if (_lockScroll) return;
double velocity = widget.scrollDirection == Axis.horizontal
? details.velocity.pixelsPerSecond.dx
: details.velocity.pixelsPerSecond.dy;
if (_animationController.value >= 0.75 || velocity > 500.0) {
if (_currentIndex <= 0 && !widget.loop) {
return;
}
_move(1.0, nextIndex: _currentIndex - 1);
} else if (_animationController.value < 0.25 || velocity < -500.0) {
if (_currentIndex >= widget.itemCount - 1 && !widget.loop) {
return;
}
_move(0.0, nextIndex: _currentIndex + 1);
} else {
_move(0.5);
}
}
void _onPanStart(DragStartDetails details) {
if (_lockScroll) return;
_currentValue = _animationController.value;
_currentPos = widget.scrollDirection == Axis.horizontal
? details.globalPosition.dx
: details.globalPosition.dy;
}
void _onPanUpdate(DragUpdateDetails details) {
if (_lockScroll) return;
double value = _currentValue +
((widget.scrollDirection == Axis.horizontal
? details.globalPosition.dx
: details.globalPosition.dy) -
_currentPos) /
_swiperWidth /
2;
// no loop ?
if (!widget.loop) {
if (_currentIndex >= widget.itemCount - 1) {
if (value < 0.5) {
value = 0.5;
}
} else if (_currentIndex <= 0) {
if (value > 0.5) {
value = 0.5;
}
}
}
_animationController.value = value;
}
int _currentIndex = 0;
}
double _getValue(List<double> values, double animationValue, int index) {
double s = values[index];
if (animationValue >= 0.5) {
if (index < values.length - 1) {
s = s + (values[index + 1] - s) * (animationValue - 0.5) * 2.0;
}
} else {
if (index != 0) {
s = s - (s - values[index - 1]) * (0.5 - animationValue) * 2.0;
}
}
return s;
}
Offset _getOffsetValue(List<Offset> values, double animationValue, int index) {
Offset s = values[index];
double dx = s.dx;
double dy = s.dy;
if (animationValue >= 0.5) {
if (index < values.length - 1) {
dx = dx + (values[index + 1].dx - dx) * (animationValue - 0.5) * 2.0;
dy = dy + (values[index + 1].dy - dy) * (animationValue - 0.5) * 2.0;
}
} else {
if (index != 0) {
dx = dx - (dx - values[index - 1].dx) * (0.5 - animationValue) * 2.0;
dy = dy - (dy - values[index - 1].dy) * (0.5 - animationValue) * 2.0;
}
}
return Offset(dx, dy);
}
abstract class TransformBuilder<T> {
List<T> values;
TransformBuilder({this.values});
Widget build(int i, double animationValue, Widget widget);
}
class ScaleTransformBuilder extends TransformBuilder<double> {
final Alignment alignment;
ScaleTransformBuilder(
{List<double> values, this.alignment = Alignment.center})
: super(values: values);
@override
Widget build(int i, double animationValue, Widget widget) {
double s = _getValue(values, animationValue, i);
return Transform.scale(scale: s, child: widget);
}
}
class OpacityTransformBuilder extends TransformBuilder<double> {
OpacityTransformBuilder({List<double> values}) : super(values: values);
@override
Widget build(int i, double animationValue, Widget widget) {
double v = _getValue(values, animationValue, i);
return Opacity(
opacity: v,
child: widget,
);
}
}
class RotateTransformBuilder extends TransformBuilder<double> {
RotateTransformBuilder({List<double> values}) : super(values: values);
@override
Widget build(int i, double animationValue, Widget widget) {
double v = _getValue(values, animationValue, i);
return Transform.rotate(
angle: v,
child: widget,
);
}
}
class TranslateTransformBuilder extends TransformBuilder<Offset> {
TranslateTransformBuilder({List<Offset> values}) : super(values: values);
@override
Widget build(int i, double animationValue, Widget widget) {
Offset s = _getOffsetValue(values, animationValue, i);
return Transform.translate(
offset: s,
child: widget,
);
}
}
class CustomLayoutOption {
final List<TransformBuilder> builders = [];
final int startIndex;
final int stateCount;
CustomLayoutOption({this.stateCount, @required this.startIndex})
: assert(startIndex != null, stateCount != null);
CustomLayoutOption addOpacity(List<double> values) {
builders.add(OpacityTransformBuilder(values: values));
return this;
}
CustomLayoutOption addTranslate(List<Offset> values) {
builders.add(TranslateTransformBuilder(values: values));
return this;
}
CustomLayoutOption addScale(List<double> values, Alignment alignment) {
builders.add(ScaleTransformBuilder(values: values, alignment: alignment));
return this;
}
CustomLayoutOption addRotate(List<double> values) {
builders.add(RotateTransformBuilder(values: values));
return this;
}
}
class _CustomLayoutSwiper extends _SubSwiper {
final CustomLayoutOption option;
const _CustomLayoutSwiper(
{@required this.option,
double itemWidth,
bool loop,
double itemHeight,
ValueChanged<int> onIndexChanged,
Key key,
IndexedWidgetBuilder itemBuilder,
Curve curve,
int duration,
int index,
int itemCount,
Axis scrollDirection,
SwiperController controller})
: assert(option != null),
super(
loop: loop,
onIndexChanged: onIndexChanged,
itemWidth: itemWidth,
itemHeight: itemHeight,
key: key,
itemBuilder: itemBuilder,
curve: curve,
duration: duration,
index: index,
itemCount: itemCount,
controller: controller,
scrollDirection: scrollDirection);
@override
State<StatefulWidget> createState() {
return _CustomLayoutState();
}
}
class _CustomLayoutState extends _CustomLayoutStateBase<_CustomLayoutSwiper> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
_startIndex = widget.option.startIndex;
_animationCount = widget.option.stateCount;
}
@override
void didUpdateWidget(_CustomLayoutSwiper oldWidget) {
_startIndex = widget.option.startIndex;
_animationCount = widget.option.stateCount;
super.didUpdateWidget(oldWidget);
}
@override
Widget _buildItem(int index, int realIndex, double animationValue) {
List<TransformBuilder> builders = widget.option.builders;
Widget child = SizedBox(
width: widget.itemWidth ?? double.infinity,
height: widget.itemHeight ?? double.infinity,
child: widget.itemBuilder(context, realIndex));
for (int i = builders.length - 1; i >= 0; --i) {
TransformBuilder builder = builders[i];
child = builder.build(index, animationValue, child);
}
return child;
}
}