初始化
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
/// This library is for swiper
|
||||
|
||||
//修改源码,适配库与pageview滚动冲突
|
||||
library card_swiper;
|
||||
|
||||
export 'src/flutter_page_indicator/flutter_page_indicator.dart';
|
||||
export 'src/swiper.dart';
|
||||
export 'src/swiper_control.dart';
|
||||
export 'src/swiper_controller.dart';
|
||||
export 'src/swiper_pagination.dart';
|
||||
export 'src/swiper_plugin.dart';
|
||||
export 'src/transformer_page_view/index_controller.dart';
|
||||
@@ -0,0 +1,456 @@
|
||||
part of 'swiper.dart';
|
||||
|
||||
abstract class _CustomLayoutStateBase<T extends _SubSwiper> extends State<T> with SingleTickerProviderStateMixin {
|
||||
late double _swiperWidth;
|
||||
late double _swiperHeight;
|
||||
late Animation<double> _animation;
|
||||
late AnimationController _animationController;
|
||||
SwiperController get _controller => widget.controller;
|
||||
late int _startIndex;
|
||||
int? _animationCount;
|
||||
int _currentIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_currentIndex = widget.index ?? 0;
|
||||
if (widget.itemWidth == null) {
|
||||
throw Exception(
|
||||
'==============\n\nwidget.itemWidth must not be null when use stack layout.\n========\n',
|
||||
);
|
||||
}
|
||||
|
||||
_createAnimationController();
|
||||
_controller.addListener(_onController);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _createAnimationController() {
|
||||
_animationController = AnimationController(vsync: this, value: 0.5);
|
||||
final tween = Tween(begin: 0.0, end: 1.0);
|
||||
_animation = tween.animate(_animationController);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback(_getSize);
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
void _getSize(Duration _) {
|
||||
if (!mounted) return;
|
||||
afterRender();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
void afterRender() {
|
||||
final renderObject = context.findRenderObject()!;
|
||||
final 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.axisDirection != oldWidget.axisDirection) {
|
||||
afterRender();
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
int _ensureIndex(int index) {
|
||||
var res = index;
|
||||
res = index % widget.itemCount;
|
||||
if (res < 0) {
|
||||
res += widget.itemCount;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@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) {
|
||||
final list = <Widget>[];
|
||||
|
||||
final animationValue = _animation.value;
|
||||
|
||||
for (var i = 0; i < _animationCount! && widget.itemCount > 0; ++i) {
|
||||
final itemIndex = _currentIndex + i + _startIndex;
|
||||
if (!widget.loop && (itemIndex >= widget.itemCount || itemIndex < 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var realIndex = itemIndex % widget.itemCount;
|
||||
if (realIndex < 0) {
|
||||
realIndex += widget.itemCount;
|
||||
}
|
||||
|
||||
if (widget.axisDirection == AxisDirection.right) {
|
||||
list.insert(0, _buildItem(i, realIndex, animationValue));
|
||||
} else {
|
||||
list.add(_buildItem(i, realIndex, animationValue));
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onHorizontalDragStart: _onPanStart,
|
||||
onHorizontalDragEnd: _onPanEnd,
|
||||
onHorizontalDragUpdate: _onPanUpdate,
|
||||
child: ClipRect(
|
||||
child: Center(
|
||||
child: _buildContainer(list),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_animationCount == null) {
|
||||
return Container();
|
||||
}
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: _buildAnimation,
|
||||
);
|
||||
}
|
||||
|
||||
late double _currentValue;
|
||||
late 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, st) {
|
||||
log('error animating _animationController', error: e, stackTrace: st);
|
||||
} finally {
|
||||
if (nextIndex != null) {
|
||||
try {
|
||||
_animationController.value = 0.5;
|
||||
} catch (e, st) {
|
||||
log(
|
||||
'error setting _animationController.value',
|
||||
error: e,
|
||||
stackTrace: st,
|
||||
);
|
||||
}
|
||||
_currentIndex = nextIndex;
|
||||
}
|
||||
_lockScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
int _getProperNewIndex(int newIndex) {
|
||||
var res = newIndex;
|
||||
if (!widget.loop && newIndex >= widget.itemCount - 1) {
|
||||
res = widget.itemCount - 1;
|
||||
} else if (!widget.loop && newIndex < 0) {
|
||||
res = 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<void> _onController() async {
|
||||
final controller = widget.controller;
|
||||
final event = controller.event;
|
||||
if (event is StepBasedIndexControllerEvent) {
|
||||
final newIndex = event.calcNextIndex(
|
||||
currentIndex: _currentIndex,
|
||||
itemCount: widget.itemCount,
|
||||
loop: widget.loop,
|
||||
reverse: false,
|
||||
);
|
||||
if (_currentIndex == newIndex) return;
|
||||
return _move(event.targetPosition, nextIndex: newIndex);
|
||||
} else if (event is MoveIndexControllerEvent) {
|
||||
final newIndex = _getProperNewIndex(event.newIndex);
|
||||
if (_currentIndex == newIndex) return;
|
||||
return _move(event.targetPosition, nextIndex: newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onPanEnd(DragEndDetails details) async {
|
||||
if (_lockScroll) return;
|
||||
|
||||
final 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(0.5);
|
||||
}
|
||||
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.5);
|
||||
}
|
||||
return _move(0.0, nextIndex: _currentIndex + 1);
|
||||
} else {
|
||||
return _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;
|
||||
var value = _currentValue +
|
||||
((widget.scrollDirection == Axis.horizontal ? details.globalPosition.dx : details.globalPosition.dy) - _currentPos) /
|
||||
_swiperWidth /
|
||||
2;
|
||||
// no loop ?
|
||||
if (!widget.loop) {
|
||||
if (widget.itemCount == 1) {
|
||||
value = 0.5;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
double _getValue(List<double> values, double animationValue, int index) {
|
||||
var 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) {
|
||||
final s = values[index];
|
||||
var dx = s.dx;
|
||||
var 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> {
|
||||
TransformBuilder({required this.values});
|
||||
|
||||
final List<T> values;
|
||||
|
||||
Widget build(int i, double animationValue, Widget widget);
|
||||
}
|
||||
|
||||
class ScaleTransformBuilder extends TransformBuilder<double> {
|
||||
ScaleTransformBuilder({
|
||||
required List<double> values,
|
||||
this.alignment = Alignment.center,
|
||||
}) : super(values: values);
|
||||
|
||||
final Alignment alignment;
|
||||
|
||||
@override
|
||||
Widget build(int i, double animationValue, Widget widget) {
|
||||
final s = _getValue(values, animationValue, i);
|
||||
return Transform.scale(scale: s, child: widget);
|
||||
}
|
||||
}
|
||||
|
||||
class OpacityTransformBuilder extends TransformBuilder<double> {
|
||||
OpacityTransformBuilder({required List<double> values}) : super(values: values);
|
||||
|
||||
@override
|
||||
Widget build(int i, double animationValue, Widget widget) {
|
||||
final v = _getValue(values, animationValue, i);
|
||||
return Opacity(
|
||||
opacity: v,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RotateTransformBuilder extends TransformBuilder<double> {
|
||||
RotateTransformBuilder({required List<double> values}) : super(values: values);
|
||||
|
||||
@override
|
||||
Widget build(int i, double animationValue, Widget widget) {
|
||||
final v = _getValue(values, animationValue, i);
|
||||
return Transform.rotate(
|
||||
angle: v,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TranslateTransformBuilder extends TransformBuilder<Offset> {
|
||||
TranslateTransformBuilder({required List<Offset> values}) : super(values: values);
|
||||
|
||||
@override
|
||||
Widget build(int i, double animationValue, Widget widget) {
|
||||
final s = _getOffsetValue(values, animationValue, i);
|
||||
return Transform.translate(
|
||||
offset: s,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomLayoutOption {
|
||||
CustomLayoutOption({this.stateCount, required this.startIndex});
|
||||
|
||||
final List<TransformBuilder<dynamic>> builders = [];
|
||||
final int startIndex;
|
||||
final int? stateCount;
|
||||
|
||||
void addOpacity(List<double> values) {
|
||||
builders.add(OpacityTransformBuilder(values: values));
|
||||
}
|
||||
|
||||
void addTranslate(List<Offset> values) {
|
||||
builders.add(TranslateTransformBuilder(values: values));
|
||||
}
|
||||
|
||||
void addScale(List<double> values, Alignment alignment) {
|
||||
builders.add(ScaleTransformBuilder(values: values, alignment: alignment));
|
||||
}
|
||||
|
||||
void addRotate(List<double> values) {
|
||||
builders.add(RotateTransformBuilder(values: values));
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomLayoutSwiper extends _SubSwiper {
|
||||
const _CustomLayoutSwiper({
|
||||
required this.option,
|
||||
double? itemWidth,
|
||||
required bool loop,
|
||||
double? itemHeight,
|
||||
ValueChanged<int>? onIndexChanged,
|
||||
Key? key,
|
||||
IndexedWidgetBuilder? itemBuilder,
|
||||
required Curve curve,
|
||||
int? duration,
|
||||
int? index,
|
||||
required int itemCount,
|
||||
Axis? scrollDirection,
|
||||
required SwiperController controller,
|
||||
}) : 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);
|
||||
|
||||
final CustomLayoutOption option;
|
||||
|
||||
@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) {
|
||||
final builders = widget.option.builders;
|
||||
|
||||
Widget child = SizedBox(
|
||||
width: widget.itemWidth ?? double.infinity,
|
||||
height: widget.itemHeight ?? double.infinity,
|
||||
child: widget.itemBuilder!(context, realIndex));
|
||||
|
||||
for (var i = builders.length - 1; i >= 0; --i) {
|
||||
final builder = builders[i];
|
||||
child = builder.build(index, animationValue, child);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ref: https://docs.flutter.dev/development/tools_base/sdk/release-notes/release-notes-3.0.0#your-code
|
||||
/// This allows a value of type T or T?
|
||||
/// to be treated as a value of type T?.
|
||||
///
|
||||
/// We use this so that APIs that have become
|
||||
/// non-nullable can still be used with `!` and `?`
|
||||
/// to support older versions of the API as well.
|
||||
T? _ambiguate<T>(T? value) => value;
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
/// page indicator library
|
||||
library flutter_page_indicator;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../transformer_page_view/transformer_page_view.dart';
|
||||
|
||||
class WarmPainter extends BasePainter {
|
||||
WarmPainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
final progress = page - index;
|
||||
final distance = size + space;
|
||||
final start = index * (size + space);
|
||||
|
||||
if (progress > 0.5) {
|
||||
final right = start + size + distance;
|
||||
//progress=>0.5-1.0
|
||||
//left:0.0=>distance
|
||||
|
||||
final left = index * distance + distance * (progress - 0.5) * 2;
|
||||
canvas.drawRRect(
|
||||
RRect.fromLTRBR(left, 0.0, right, size, Radius.circular(radius)),
|
||||
_paint);
|
||||
} else {
|
||||
final right = start + size + distance * progress * 2;
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromLTRBR(start, 0.0, right, size, Radius.circular(radius)),
|
||||
_paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DropPainter extends BasePainter {
|
||||
DropPainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
final progress = page - index;
|
||||
final dropHeight = widget.dropHeight;
|
||||
final rate = (0.5 - progress).abs() * 2;
|
||||
final scale = widget.scale;
|
||||
|
||||
//lerp(begin, end, progress)
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset(radius + ((page) * (size + space)),
|
||||
radius - dropHeight * (1 - rate)),
|
||||
radius * (scale + rate * (1.0 - scale)),
|
||||
_paint);
|
||||
}
|
||||
}
|
||||
|
||||
class NonePainter extends BasePainter {
|
||||
NonePainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
final progress = page - index;
|
||||
final secondOffset = index == widget.count - 1
|
||||
? radius
|
||||
: radius + ((index + 1) * (size + space));
|
||||
|
||||
if (progress > 0.5) {
|
||||
canvas.drawCircle(Offset(secondOffset, radius), radius, _paint);
|
||||
} else {
|
||||
canvas.drawCircle(
|
||||
Offset(radius + (index * (size + space)), radius), radius, _paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SlidePainter extends BasePainter {
|
||||
SlidePainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
canvas.drawCircle(
|
||||
Offset(radius + (page * (size + space)), radius), radius, _paint);
|
||||
}
|
||||
}
|
||||
|
||||
class ScalePainter extends BasePainter {
|
||||
ScalePainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
bool _shouldSkip(int index) {
|
||||
if (this.index == widget.count - 1) {
|
||||
return index == 0 || index == this.index;
|
||||
}
|
||||
return (index == this.index || index == this.index + 1);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
_paint.color = widget.color;
|
||||
final space = widget.space;
|
||||
final size = widget.size;
|
||||
final radius = size / 2;
|
||||
final c = widget.count;
|
||||
for (var i = 0; i < c; ++i) {
|
||||
if (_shouldSkip(i)) {
|
||||
continue;
|
||||
}
|
||||
canvas.drawCircle(Offset(i * (size + space) + radius, radius),
|
||||
radius * widget.scale, _paint);
|
||||
}
|
||||
|
||||
_paint.color = widget.activeColor;
|
||||
draw(canvas, space, size, radius);
|
||||
}
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
final secondOffset = index == widget.count - 1
|
||||
? radius
|
||||
: radius + ((index + 1) * (size + space));
|
||||
|
||||
final progress = page - index;
|
||||
_paint.color = Color.lerp(widget.activeColor, widget.color, progress)!;
|
||||
//last
|
||||
canvas.drawCircle(Offset(radius + (index * (size + space)), radius),
|
||||
lerp(radius, radius * widget.scale, progress), _paint);
|
||||
//first
|
||||
_paint.color = Color.lerp(widget.color, widget.activeColor, progress)!;
|
||||
canvas.drawCircle(Offset(secondOffset, radius),
|
||||
lerp(radius * widget.scale, radius, progress), _paint);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorPainter extends BasePainter {
|
||||
ColorPainter(PageIndicator widget, double page, int index, Paint paint)
|
||||
: super(widget, page, index, paint);
|
||||
|
||||
@override
|
||||
bool _shouldSkip(int index) {
|
||||
if (this.index == widget.count - 1) {
|
||||
return index == 0 || index == this.index;
|
||||
}
|
||||
return (index == this.index || index == this.index + 1);
|
||||
}
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas, double space, double size, double radius) {
|
||||
final progress = page - index;
|
||||
final secondOffset = index == widget.count - 1
|
||||
? radius
|
||||
: radius + ((index + 1) * (size + space));
|
||||
|
||||
_paint.color = Color.lerp(widget.activeColor, widget.color, progress)!;
|
||||
//left
|
||||
canvas.drawCircle(
|
||||
Offset(radius + (index * (size + space)), radius), radius, _paint);
|
||||
//right
|
||||
_paint.color = Color.lerp(widget.color, widget.activeColor, progress)!;
|
||||
canvas.drawCircle(Offset(secondOffset, radius), radius, _paint);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BasePainter extends CustomPainter {
|
||||
BasePainter(this.widget, this.page, this.index, this._paint);
|
||||
|
||||
final PageIndicator widget;
|
||||
final double page;
|
||||
final int index;
|
||||
final Paint _paint;
|
||||
|
||||
double lerp(double begin, double end, double progress) {
|
||||
return begin + (end - begin) * progress;
|
||||
}
|
||||
|
||||
void draw(Canvas canvas, double space, double size, double radius);
|
||||
|
||||
bool _shouldSkip(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//double secondOffset = index == widget.count-1 ? radius : radius + ((index + 1) * (size + space));
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
_paint.color = widget.color;
|
||||
final space = widget.space;
|
||||
final size = widget.size;
|
||||
final radius = size / 2;
|
||||
final c = widget.count;
|
||||
for (var i = 0; i < c; ++i) {
|
||||
if (_shouldSkip(i)) {
|
||||
continue;
|
||||
}
|
||||
canvas.drawCircle(
|
||||
Offset(i * (size + space) + radius, radius), radius, _paint);
|
||||
}
|
||||
|
||||
var page = this.page;
|
||||
if (page < index) {
|
||||
page = 0.0;
|
||||
}
|
||||
_paint.color = widget.activeColor;
|
||||
draw(canvas, space, size, radius);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(BasePainter oldDelegate) {
|
||||
return oldDelegate.page != page;
|
||||
}
|
||||
}
|
||||
|
||||
class _PageIndicatorState extends State<PageIndicator> {
|
||||
int index = 0;
|
||||
double page = 0;
|
||||
final _paint = Paint();
|
||||
|
||||
BasePainter _createPainter() {
|
||||
switch (widget.layout) {
|
||||
case PageIndicatorLayout.NONE:
|
||||
return NonePainter(widget, page, index, _paint);
|
||||
case PageIndicatorLayout.SLIDE:
|
||||
return SlidePainter(widget, page, index, _paint);
|
||||
case PageIndicatorLayout.WARM:
|
||||
return WarmPainter(widget, page, index, _paint);
|
||||
case PageIndicatorLayout.COLOR:
|
||||
return ColorPainter(widget, page, index, _paint);
|
||||
case PageIndicatorLayout.SCALE:
|
||||
return ScalePainter(widget, page, index, _paint);
|
||||
case PageIndicatorLayout.DROP:
|
||||
return DropPainter(widget, page, index, _paint);
|
||||
default:
|
||||
throw Exception('Not a valid layout');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = SizedBox(
|
||||
width: widget.count * widget.size + (widget.count - 1) * widget.space,
|
||||
height: widget.size,
|
||||
child: CustomPaint(
|
||||
painter: _createPainter(),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.layout == PageIndicatorLayout.SCALE ||
|
||||
widget.layout == PageIndicatorLayout.COLOR) {
|
||||
child = ClipRect(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return IgnorePointer(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _setInitialPage() {
|
||||
// use the initial page index but cut off
|
||||
// the offset specified when looping (kMiddleValue)
|
||||
index = widget.controller.initialPage % kMiddleValue;
|
||||
page = index.toDouble();
|
||||
}
|
||||
|
||||
void _onController() {
|
||||
if (!widget.controller.hasClients) return;
|
||||
page = widget.controller.page ?? 0.0;
|
||||
index = page.floor();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.controller.addListener(_onController);
|
||||
_setInitialPage();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PageIndicator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.controller != oldWidget.controller) {
|
||||
oldWidget.controller.removeListener(_onController);
|
||||
widget.controller.addListener(_onController);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_onController);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
enum PageIndicatorLayout {
|
||||
NONE,
|
||||
SLIDE,
|
||||
WARM,
|
||||
COLOR,
|
||||
SCALE,
|
||||
DROP,
|
||||
}
|
||||
|
||||
class PageIndicator extends StatefulWidget {
|
||||
const PageIndicator({
|
||||
Key? key,
|
||||
this.size = 20.0,
|
||||
this.space = 5.0,
|
||||
required this.count,
|
||||
this.activeSize = 20.0,
|
||||
required this.controller,
|
||||
this.color = Colors.white30,
|
||||
this.layout = PageIndicatorLayout.SLIDE,
|
||||
this.activeColor = Colors.white,
|
||||
this.scale = 0.6,
|
||||
this.dropHeight = 20.0,
|
||||
}) : super(key: key);
|
||||
|
||||
/// size of the dots
|
||||
final double size;
|
||||
|
||||
/// space between dots.
|
||||
final double space;
|
||||
|
||||
/// count of dots
|
||||
final int count;
|
||||
|
||||
/// active color
|
||||
final Color activeColor;
|
||||
|
||||
/// normal color
|
||||
final Color color;
|
||||
|
||||
/// layout of the dots,default is [PageIndicatorLayout.SLIDE]
|
||||
final PageIndicatorLayout? layout;
|
||||
|
||||
// Only valid when layout==PageIndicatorLayout.scale
|
||||
final double scale;
|
||||
|
||||
// Only valid when layout==PageIndicatorLayout.drop
|
||||
final double dropHeight;
|
||||
|
||||
final PageController controller;
|
||||
|
||||
final double activeSize;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _PageIndicatorState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,996 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../card_swiper.dart';
|
||||
import 'transformer_page_view/transformer_page_view.dart';
|
||||
|
||||
part 'custom_layout.dart';
|
||||
|
||||
typedef SwiperOnTap = void Function(int index);
|
||||
|
||||
typedef SwiperDataBuilder<T> = Widget Function(
|
||||
BuildContext context,
|
||||
T 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;
|
||||
|
||||
enum SwiperLayout {
|
||||
DEFAULT,
|
||||
STACK,
|
||||
TINDER,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
class Swiper extends StatefulWidget {
|
||||
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.axisDirection = AxisDirection.left,
|
||||
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,
|
||||
this.allowImplicitScrolling = false,
|
||||
}) : 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 autoplayDisableOnInteraction = true,
|
||||
int duration = kDefaultAutoplayTransactionDuration,
|
||||
ValueChanged<int>? onIndexChanged,
|
||||
int? index,
|
||||
SwiperOnTap? onTap,
|
||||
bool loop = true,
|
||||
Curve curve = Curves.ease,
|
||||
Axis scrollDirection = Axis.horizontal,
|
||||
AxisDirection axisDirection = AxisDirection.left,
|
||||
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,
|
||||
double? fade,
|
||||
PageIndicatorLayout indicatorLayout = PageIndicatorLayout.NONE,
|
||||
SwiperLayout layout = SwiperLayout.DEFAULT,
|
||||
}) =>
|
||||
Swiper(
|
||||
fade: fade,
|
||||
indicatorLayout: indicatorLayout,
|
||||
layout: layout,
|
||||
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,
|
||||
axisDirection: axisDirection,
|
||||
pagination: pagination,
|
||||
control: control,
|
||||
controller: controller,
|
||||
loop: loop,
|
||||
plugins: plugins,
|
||||
physics: physics,
|
||||
key: key,
|
||||
itemBuilder: (context, index) {
|
||||
return children[index];
|
||||
},
|
||||
itemCount: children.length,
|
||||
);
|
||||
|
||||
/// 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;
|
||||
|
||||
///left/right for Stack Layout
|
||||
final AxisDirection axisDirection;
|
||||
|
||||
///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;
|
||||
|
||||
final bool allowImplicitScrolling;
|
||||
|
||||
static Swiper list<T>({
|
||||
PageTransformer? transformer,
|
||||
required List<T> list,
|
||||
CustomLayoutOption? customLayoutOption,
|
||||
required SwiperDataBuilder<T> 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,
|
||||
AxisDirection axisDirection = AxisDirection.left,
|
||||
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,
|
||||
double? fade,
|
||||
PageIndicatorLayout indicatorLayout = PageIndicatorLayout.NONE,
|
||||
SwiperLayout layout = SwiperLayout.DEFAULT,
|
||||
}) =>
|
||||
Swiper(
|
||||
fade: fade,
|
||||
indicatorLayout: indicatorLayout,
|
||||
layout: layout,
|
||||
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,
|
||||
axisDirection: axisDirection,
|
||||
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() => _SwiperState();
|
||||
}
|
||||
|
||||
abstract class _SwiperTimerMixin extends State<Swiper> {
|
||||
Timer? _timer;
|
||||
late SwiperController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = widget.controller ?? SwiperController();
|
||||
_controller.addListener(_onController);
|
||||
if (widget.autoplay) {
|
||||
_controller.startAutoplay();
|
||||
} else {
|
||||
_controller.stopAutoplay();
|
||||
}
|
||||
}
|
||||
|
||||
void _onController() {
|
||||
final event = _controller.event;
|
||||
if (event is AutoPlaySwiperControllerEvent) {
|
||||
if (event.autoplay) {
|
||||
if (_timer == null) {
|
||||
_startAutoplay();
|
||||
}
|
||||
} else {
|
||||
_stopAutoplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(Swiper oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (_controller != oldWidget.controller) {
|
||||
final oldController = oldWidget.controller;
|
||||
if (oldController != null) {
|
||||
oldController.removeListener(_onController);
|
||||
_controller = oldController;
|
||||
_controller.addListener(_onController);
|
||||
}
|
||||
}
|
||||
if (widget.autoplay != oldWidget.autoplay) {
|
||||
if (widget.autoplay) {
|
||||
_controller.startAutoplay();
|
||||
} else {
|
||||
_controller.stopAutoplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.removeListener(_onController);
|
||||
_stopAutoplay();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startAutoplay() {
|
||||
_stopAutoplay();
|
||||
_timer = Timer.periodic(
|
||||
Duration(
|
||||
milliseconds: widget.autoplayDelay,
|
||||
),
|
||||
_onTimer,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onTimer(Timer timer) async {
|
||||
return _controller.next(animation: true);
|
||||
}
|
||||
|
||||
void _stopAutoplay() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
class _SwiperState extends _SwiperTimerMixin {
|
||||
late 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() {
|
||||
super.initState();
|
||||
_activeIndex = widget.index ?? widget.controller?.index ?? 0;
|
||||
if (_isPageViewLayout()) {
|
||||
_pageController = TransformerPageController(
|
||||
initialPage: widget.index ?? widget.controller?.index ?? 0,
|
||||
loop: widget.loop,
|
||||
itemCount: widget.itemCount,
|
||||
reverse: widget.transformer?.reverse ?? false,
|
||||
viewportFraction: widget.viewportFraction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isPageViewLayout() {
|
||||
return widget.layout == SwiperLayout.DEFAULT;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
bool _getReverse(Swiper widget) => widget.transformer?.reverse ?? false;
|
||||
|
||||
@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 ?? widget.controller?.index ?? 0,
|
||||
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;
|
||||
});
|
||||
|
||||
final event = _controller.event;
|
||||
if ((event is MoveIndexControllerEvent) && (event.newIndex != index)) {
|
||||
return;
|
||||
}
|
||||
widget.onIndexChanged?.call(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,
|
||||
axisDirection: widget.axisDirection,
|
||||
);
|
||||
} else if (_isPageViewLayout()) {
|
||||
//default
|
||||
var transformer = widget.transformer;
|
||||
if (widget.scale != null || widget.fade != null) {
|
||||
transformer = ScaleAndFadeTransformer(scale: widget.scale, fade: widget.fade);
|
||||
}
|
||||
|
||||
final 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,
|
||||
allowImplicitScrolling: widget.allowImplicitScrolling,
|
||||
);
|
||||
if (widget.autoplayDisableOnInteraction && widget.autoplay) {
|
||||
return NotificationListener(
|
||||
onNotification: (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;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
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 const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
SwiperPluginConfig _ensureConfig(SwiperPluginConfig? config) {
|
||||
final con = config ??
|
||||
SwiperPluginConfig(
|
||||
outer: widget.outer,
|
||||
itemCount: widget.itemCount,
|
||||
layout: widget.layout,
|
||||
indicatorLayout: widget.indicatorLayout,
|
||||
pageController: _pageController,
|
||||
activeIndex: _activeIndex,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
axisDirection: widget.axisDirection,
|
||||
controller: _controller,
|
||||
loop: widget.loop,
|
||||
);
|
||||
|
||||
return con;
|
||||
}
|
||||
|
||||
List<Widget>? _ensureListForStack({
|
||||
required Widget swiper,
|
||||
required List<Widget>? listForStack,
|
||||
required Widget widget,
|
||||
}) {
|
||||
final resList = <Widget>[];
|
||||
if (listForStack == null) {
|
||||
resList.addAll([swiper, widget]);
|
||||
} else {
|
||||
resList.addAll([...listForStack, widget]);
|
||||
}
|
||||
return resList;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final swiper = _buildSwiper();
|
||||
List<Widget>? listForStack;
|
||||
SwiperPluginConfig? config;
|
||||
if (widget.control != null) {
|
||||
//Stack
|
||||
config = _ensureConfig(config);
|
||||
listForStack = _ensureListForStack(
|
||||
swiper: swiper,
|
||||
listForStack: listForStack,
|
||||
widget: widget.control!.build(context, config),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.plugins != null) {
|
||||
config = _ensureConfig(config);
|
||||
for (final plugin in widget.plugins!) {
|
||||
listForStack = _ensureListForStack(
|
||||
swiper: swiper,
|
||||
listForStack: listForStack,
|
||||
widget: 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: swiper,
|
||||
listForStack: listForStack,
|
||||
widget: widget.pagination!.build(context, config),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (listForStack != null) {
|
||||
return Stack(
|
||||
children: listForStack,
|
||||
);
|
||||
}
|
||||
|
||||
return swiper;
|
||||
}
|
||||
|
||||
Widget _buildOuterPagination(
|
||||
SwiperPagination pagination,
|
||||
Widget swiper,
|
||||
SwiperPluginConfig config,
|
||||
) {
|
||||
final list = <Widget>[];
|
||||
//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(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: list,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SubSwiper extends StatefulWidget {
|
||||
const _SubSwiper({
|
||||
Key? key,
|
||||
required this.loop,
|
||||
this.itemHeight,
|
||||
this.itemWidth,
|
||||
this.duration,
|
||||
required this.curve,
|
||||
this.itemBuilder,
|
||||
required this.controller,
|
||||
this.index,
|
||||
required this.itemCount,
|
||||
this.scrollDirection = Axis.horizontal,
|
||||
this.axisDirection = AxisDirection.left,
|
||||
this.onIndexChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
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;
|
||||
final AxisDirection? axisDirection;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState();
|
||||
|
||||
int getCorrectIndex(int indexNeedsFix) {
|
||||
if (itemCount == 0) return 0;
|
||||
var value = indexNeedsFix % itemCount;
|
||||
if (value < 0) {
|
||||
value += itemCount;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class _TinderSwiper extends _SubSwiper {
|
||||
const _TinderSwiper({
|
||||
Key? key,
|
||||
required Curve curve,
|
||||
int? duration,
|
||||
required SwiperController controller,
|
||||
ValueChanged<int>? onIndexChanged,
|
||||
double? itemHeight,
|
||||
double? itemWidth,
|
||||
IndexedWidgetBuilder? itemBuilder,
|
||||
int? index,
|
||||
required bool loop,
|
||||
required 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,
|
||||
required Curve curve,
|
||||
int? duration,
|
||||
required SwiperController controller,
|
||||
ValueChanged<int>? onIndexChanged,
|
||||
double? itemHeight,
|
||||
double? itemWidth,
|
||||
IndexedWidgetBuilder? itemBuilder,
|
||||
int? index,
|
||||
required bool loop,
|
||||
required int itemCount,
|
||||
Axis? scrollDirection,
|
||||
AxisDirection? axisDirection,
|
||||
}) : 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,
|
||||
axisDirection: axisDirection,
|
||||
);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _StackViewState();
|
||||
}
|
||||
|
||||
class _TinderState extends _CustomLayoutStateBase<_TinderSwiper> {
|
||||
late List<double> scales;
|
||||
late List<double> offsetsX;
|
||||
late List<double> offsetsY;
|
||||
late List<double> opacity;
|
||||
late List<double> rotates;
|
||||
|
||||
double getOffsetY(double scale) {
|
||||
return widget.itemHeight! - widget.itemHeight! * scale;
|
||||
}
|
||||
|
||||
@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) {
|
||||
final s = _getValue(scales, animationValue, i);
|
||||
final f = _getValue(offsetsX, animationValue, i);
|
||||
final fy = _getValue(offsetsY, animationValue, i);
|
||||
final o = _getValue(opacity, animationValue, i);
|
||||
final a = _getValue(rotates, animationValue, i);
|
||||
|
||||
final 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> {
|
||||
late List<double> scales;
|
||||
late List<double> offsets;
|
||||
late List<double> opacity;
|
||||
|
||||
void _updateValues() {
|
||||
if (widget.scrollDirection == Axis.horizontal) {
|
||||
final space = (_swiperWidth - widget.itemWidth!) / 2;
|
||||
offsets = widget.axisDirection == AxisDirection.left
|
||||
? [-space, -space / 3 * 2, -space / 3, 0.0, _swiperWidth]
|
||||
: [_swiperWidth, 0.0, -space / 3, -space / 3 * 2, -space];
|
||||
} else {
|
||||
final 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();
|
||||
final isRightSide = widget.axisDirection == AxisDirection.right;
|
||||
|
||||
//length of the values array below
|
||||
_animationCount = 5;
|
||||
|
||||
//Array below this line, '0' index is 1.0, which is the first item show in swiper.
|
||||
_startIndex = isRightSide ? -1 : -3;
|
||||
scales = isRightSide ? [1.0, 1.0, 0.9, 0.8, 0.7] : [0.7, 0.8, 0.9, 1.0, 1.0];
|
||||
opacity = isRightSide ? [1.0, 1.0, 1.0, 0.5, 0.0] : [0.0, 0.5, 1.0, 1.0, 1.0];
|
||||
|
||||
_updateValues();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget _buildItem(int i, int realIndex, double animationValue) {
|
||||
final s = _getValue(scales, animationValue, i);
|
||||
final f = _getValue(offsets, animationValue, i);
|
||||
final o = _getValue(opacity, animationValue, i);
|
||||
|
||||
final offset = widget.scrollDirection == Axis.horizontal
|
||||
? widget.axisDirection == AxisDirection.left
|
||||
? Offset(f, 0.0)
|
||||
: Offset(-f, 0.0)
|
||||
: Offset(0.0, f);
|
||||
|
||||
final alignment = widget.scrollDirection == Axis.horizontal
|
||||
? widget.axisDirection == AxisDirection.left
|
||||
? Alignment.centerLeft
|
||||
: Alignment.centerRight
|
||||
: 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 {
|
||||
ScaleAndFadeTransformer({double? fade = 0.3, double? scale = 0.8})
|
||||
: _fade = fade,
|
||||
_scale = scale;
|
||||
|
||||
final double? _scale;
|
||||
final double? _fade;
|
||||
|
||||
@override
|
||||
Widget transform(Widget child, TransformInfo info) {
|
||||
final position = info.position;
|
||||
var c = child;
|
||||
if (_scale != null) {
|
||||
final scaleFactor = (1 - position!.abs()) * (1 - _scale!);
|
||||
final scale = _scale! + scaleFactor;
|
||||
|
||||
c = Transform.scale(
|
||||
scale: scale,
|
||||
child: c,
|
||||
);
|
||||
}
|
||||
|
||||
if (_fade != null) {
|
||||
final fadeFactor = (1 - position!.abs()) * (1 - _fade!);
|
||||
final opacity = _fade! + fadeFactor;
|
||||
c = Opacity(
|
||||
opacity: opacity,
|
||||
child: c,
|
||||
);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/tools_base/widget/card_swiper/src/swiper_plugin.dart';
|
||||
|
||||
class SwiperControl extends SwiperPlugin {
|
||||
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),
|
||||
});
|
||||
|
||||
///IconData for previous
|
||||
final IconData iconPrevious;
|
||||
|
||||
///iconData for 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;
|
||||
|
||||
Widget buildButton({
|
||||
required SwiperPluginConfig? config,
|
||||
required Color color,
|
||||
required IconData iconData,
|
||||
required int quarterTurns,
|
||||
required bool previous,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async {
|
||||
if (previous) {
|
||||
await config!.controller.previous(animation: true);
|
||||
} else {
|
||||
await config!.controller.next(animation: true);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: RotatedBox(
|
||||
quarterTurns: quarterTurns,
|
||||
child: Icon(
|
||||
iconData,
|
||||
semanticLabel: previous ? 'Previous' : 'Next',
|
||||
size: size,
|
||||
color: color,
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig config) {
|
||||
final themeData = Theme.of(context);
|
||||
|
||||
final color = this.color ?? themeData.primaryColor;
|
||||
final disableColor = this.disableColor ?? themeData.disabledColor;
|
||||
Color prevColor;
|
||||
Color nextColor;
|
||||
|
||||
if (config.loop) {
|
||||
prevColor = nextColor = color;
|
||||
} else {
|
||||
final next = config.activeIndex < config.itemCount - 1;
|
||||
final 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: config,
|
||||
color: prevColor,
|
||||
iconData: iconPrevious,
|
||||
quarterTurns: 0,
|
||||
previous: true,
|
||||
),
|
||||
buildButton(
|
||||
config: config,
|
||||
color: nextColor,
|
||||
iconData: iconNext,
|
||||
quarterTurns: 0,
|
||||
previous: false,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
child = Column(
|
||||
key: key,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
buildButton(
|
||||
config: config,
|
||||
color: prevColor,
|
||||
iconData: iconPrevious,
|
||||
quarterTurns: -3,
|
||||
previous: true,
|
||||
),
|
||||
buildButton(
|
||||
config: config,
|
||||
color: nextColor,
|
||||
iconData: iconNext,
|
||||
quarterTurns: -3,
|
||||
previous: false,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox.expand(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'swiper_plugin.dart';
|
||||
import 'transformer_page_view/index_controller.dart';
|
||||
|
||||
class SwipeIndexControllerEvent extends IndexControllerEventBase {
|
||||
SwipeIndexControllerEvent({
|
||||
required this.pos,
|
||||
required bool animation,
|
||||
}) : super(animation: animation);
|
||||
final double pos;
|
||||
}
|
||||
|
||||
class BuildIndexControllerEvent extends IndexControllerEventBase {
|
||||
BuildIndexControllerEvent({
|
||||
required bool animation,
|
||||
required this.config,
|
||||
}) : super(animation: animation);
|
||||
final SwiperPluginConfig config;
|
||||
}
|
||||
|
||||
class AutoPlaySwiperControllerEvent extends IndexControllerEventBase {
|
||||
AutoPlaySwiperControllerEvent({
|
||||
required bool animation,
|
||||
required this.autoplay,
|
||||
}) : super(animation: animation);
|
||||
|
||||
AutoPlaySwiperControllerEvent.start({
|
||||
required bool animation,
|
||||
}) : this(animation: animation, autoplay: true);
|
||||
AutoPlaySwiperControllerEvent.stop({
|
||||
required bool animation,
|
||||
}) : this(animation: animation, autoplay: false);
|
||||
final bool autoplay;
|
||||
}
|
||||
|
||||
class SwiperController extends IndexController {
|
||||
void startAutoplay({bool animation = true}) {
|
||||
event = AutoPlaySwiperControllerEvent.start(animation: animation);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void stopAutoplay({bool animation = true}) {
|
||||
event = AutoPlaySwiperControllerEvent.stop(animation: animation);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../card_swiper.dart';
|
||||
|
||||
class FractionPaginationBuilder extends SwiperPlugin {
|
||||
const FractionPaginationBuilder({
|
||||
this.color,
|
||||
this.fontSize = 20.0,
|
||||
this.key,
|
||||
this.activeColor,
|
||||
this.activeFontSize = 35.0,
|
||||
});
|
||||
|
||||
///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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig? config) {
|
||||
final themeData = Theme.of(context);
|
||||
final activeColor = this.activeColor ?? themeData.primaryColor;
|
||||
final 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 {
|
||||
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,
|
||||
});
|
||||
|
||||
///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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig config) {
|
||||
final themeData = Theme.of(context);
|
||||
final activeColor = this.activeColor ?? themeData.primaryColor;
|
||||
final color = this.color ?? themeData.scaffoldBackgroundColor;
|
||||
|
||||
final list = <Widget>[];
|
||||
|
||||
final itemCount = config.itemCount;
|
||||
final activeIndex = config.activeIndex;
|
||||
if (itemCount > 20) {
|
||||
log(
|
||||
'The itemCount is too big, we suggest use FractionPaginationBuilder '
|
||||
'instead of DotSwiperPaginationBuilder in this situation',
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < itemCount; ++i) {
|
||||
final active = i == activeIndex;
|
||||
final 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 {
|
||||
const DotSwiperPaginationBuilder({
|
||||
this.activeColor,
|
||||
this.color,
|
||||
this.key,
|
||||
this.size = 10.0,
|
||||
this.activeSize = 10.0,
|
||||
this.space = 3.0,
|
||||
});
|
||||
|
||||
///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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig config) {
|
||||
if (config.itemCount > 20) {
|
||||
log(
|
||||
'The itemCount is too big, we suggest use FractionPaginationBuilder '
|
||||
'instead of DotSwiperPaginationBuilder in this situation',
|
||||
);
|
||||
}
|
||||
var activeColor = this.activeColor;
|
||||
var color = this.color;
|
||||
|
||||
if (activeColor == null || color == null) {
|
||||
final 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,
|
||||
);
|
||||
}
|
||||
|
||||
final list = <Widget>[];
|
||||
|
||||
final itemCount = config.itemCount;
|
||||
final activeIndex = config.activeIndex;
|
||||
|
||||
for (var i = 0; i < itemCount; ++i) {
|
||||
final 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 {
|
||||
const SwiperCustomPagination({required this.builder});
|
||||
|
||||
final SwiperPaginationBuilder builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig config) {
|
||||
return builder(context, config);
|
||||
}
|
||||
}
|
||||
|
||||
class SwiperPagination extends SwiperPlugin {
|
||||
const SwiperPagination({
|
||||
this.alignment,
|
||||
this.key,
|
||||
this.margin = const EdgeInsets.all(10.0),
|
||||
this.builder = SwiperPagination.dots,
|
||||
});
|
||||
|
||||
/// 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 widget
|
||||
final SwiperPlugin builder;
|
||||
|
||||
final Key? key;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, SwiperPluginConfig config) {
|
||||
final defaultAlignment = 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 ?? defaultAlignment,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../card_swiper.dart';
|
||||
|
||||
/// plugin to display swiper components
|
||||
///
|
||||
abstract class SwiperPlugin {
|
||||
const SwiperPlugin();
|
||||
|
||||
Widget build(BuildContext context, SwiperPluginConfig config);
|
||||
}
|
||||
|
||||
class SwiperPluginConfig {
|
||||
const SwiperPluginConfig({
|
||||
required this.scrollDirection,
|
||||
required this.controller,
|
||||
required this.activeIndex,
|
||||
required this.itemCount,
|
||||
this.axisDirection,
|
||||
this.indicatorLayout,
|
||||
this.outer,
|
||||
this.pageController,
|
||||
this.layout,
|
||||
this.loop = false,
|
||||
});
|
||||
|
||||
final Axis scrollDirection;
|
||||
final AxisDirection? axisDirection;
|
||||
final SwiperController controller;
|
||||
final int activeIndex;
|
||||
final int itemCount;
|
||||
final PageIndicatorLayout? indicatorLayout;
|
||||
final bool loop;
|
||||
final bool? outer;
|
||||
final PageController? pageController;
|
||||
final SwiperLayout? layout;
|
||||
}
|
||||
|
||||
class SwiperPluginView extends StatelessWidget {
|
||||
const SwiperPluginView({
|
||||
Key? key,
|
||||
required this.plugin,
|
||||
required this.config,
|
||||
}) : super(key: key);
|
||||
|
||||
final SwiperPlugin plugin;
|
||||
final SwiperPluginConfig config;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return plugin.build(context, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
abstract class IndexControllerEventBase {
|
||||
IndexControllerEventBase({
|
||||
required this.animation,
|
||||
});
|
||||
|
||||
final bool animation;
|
||||
|
||||
final completer = Completer<void>();
|
||||
Future<void> get future => completer.future;
|
||||
void complete() {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin TargetedPositionControllerEvent on IndexControllerEventBase {
|
||||
double get targetPosition;
|
||||
}
|
||||
mixin StepBasedIndexControllerEvent on TargetedPositionControllerEvent {
|
||||
int get step;
|
||||
int calcNextIndex({
|
||||
required int currentIndex,
|
||||
required int itemCount,
|
||||
required bool loop,
|
||||
required bool reverse,
|
||||
}) {
|
||||
var cIndex = currentIndex;
|
||||
if (reverse) {
|
||||
cIndex -= step;
|
||||
} else {
|
||||
cIndex += step;
|
||||
}
|
||||
|
||||
if (!loop) {
|
||||
if (cIndex >= itemCount) {
|
||||
cIndex = itemCount - 1;
|
||||
} else if (cIndex < 0) {
|
||||
cIndex = 0;
|
||||
}
|
||||
}
|
||||
return cIndex;
|
||||
}
|
||||
}
|
||||
|
||||
class NextIndexControllerEvent extends IndexControllerEventBase
|
||||
with TargetedPositionControllerEvent, StepBasedIndexControllerEvent {
|
||||
NextIndexControllerEvent({
|
||||
required bool animation,
|
||||
}) : super(
|
||||
animation: animation,
|
||||
);
|
||||
|
||||
@override
|
||||
int get step => 1;
|
||||
|
||||
@override
|
||||
double get targetPosition => 0;
|
||||
}
|
||||
|
||||
class PrevIndexControllerEvent extends IndexControllerEventBase
|
||||
with TargetedPositionControllerEvent, StepBasedIndexControllerEvent {
|
||||
PrevIndexControllerEvent({
|
||||
required bool animation,
|
||||
}) : super(
|
||||
animation: animation,
|
||||
);
|
||||
@override
|
||||
int get step => -1;
|
||||
|
||||
@override
|
||||
double get targetPosition => 1;
|
||||
}
|
||||
|
||||
class MoveIndexControllerEvent extends IndexControllerEventBase
|
||||
with TargetedPositionControllerEvent {
|
||||
MoveIndexControllerEvent({
|
||||
required this.newIndex,
|
||||
required this.oldIndex,
|
||||
required bool animation,
|
||||
}) : super(
|
||||
animation: animation,
|
||||
);
|
||||
final int newIndex;
|
||||
final int oldIndex;
|
||||
@override
|
||||
double get targetPosition => newIndex > oldIndex ? 1 : 0;
|
||||
}
|
||||
|
||||
class IndexController extends ChangeNotifier {
|
||||
IndexControllerEventBase? event;
|
||||
int index = 0;
|
||||
Future<void> move(int index, {bool animation = true}) {
|
||||
final e = event = MoveIndexControllerEvent(
|
||||
animation: animation,
|
||||
newIndex: index,
|
||||
oldIndex: this.index,
|
||||
);
|
||||
notifyListeners();
|
||||
return e.future;
|
||||
}
|
||||
|
||||
Future<void> next({bool animation = true}) {
|
||||
final e = event = NextIndexControllerEvent(animation: animation);
|
||||
notifyListeners();
|
||||
return e.future;
|
||||
}
|
||||
|
||||
Future<void> previous({bool animation = true}) {
|
||||
final e = event = PrevIndexControllerEvent(animation: animation);
|
||||
notifyListeners();
|
||||
return e.future;
|
||||
}
|
||||
}
|
||||
+611
@@ -0,0 +1,611 @@
|
||||
/// transformer page view library
|
||||
library transformer_page_view;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'index_controller.dart';
|
||||
|
||||
///
|
||||
/// NOTICE::
|
||||
///
|
||||
/// In order to make package smaller,currently we're not supporting any build-in page transformers
|
||||
/// You can find build in transforms here:
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
const int kMaxValue = 2000000000;
|
||||
const int kMiddleValue = 1000000000;
|
||||
|
||||
/// Default auto play transition duration (in millisecond)
|
||||
const int kDefaultTransactionDuration = 300;
|
||||
|
||||
class TransformInfo {
|
||||
TransformInfo({
|
||||
this.index,
|
||||
this.position,
|
||||
this.width,
|
||||
this.height,
|
||||
this.activeIndex,
|
||||
required this.fromIndex,
|
||||
this.forward,
|
||||
this.done,
|
||||
this.viewportFraction,
|
||||
this.scrollDirection,
|
||||
});
|
||||
|
||||
/// The `width` of the `TransformerPageView`
|
||||
final double? width;
|
||||
|
||||
/// The `height` of the `TransformerPageView`
|
||||
final double? height;
|
||||
|
||||
/// The `position` of the widget pass to [PageTransformer.transform]
|
||||
/// A `position` describes how visible the widget is.
|
||||
/// The widget in the center of the screen' which is full visible, position is 0.0.
|
||||
/// The widget in the left ,may be hidden, of the screen's position is less than 0.0, -1.0 when out of the screen.
|
||||
/// The widget in the right ,may be hidden, of the screen's position is greater than 0.0, 1.0 when out of the screen
|
||||
///
|
||||
///
|
||||
final double? position;
|
||||
|
||||
/// The `index` of the widget pass to [PageTransformer.transform]
|
||||
final int? index;
|
||||
|
||||
/// The `activeIndex` of the PageView
|
||||
final int? activeIndex;
|
||||
|
||||
/// The `activeIndex` of the PageView, from user start to swipe
|
||||
/// It will change when user end drag
|
||||
final int fromIndex;
|
||||
|
||||
/// Next `index` is greater than this `index`
|
||||
final bool? forward;
|
||||
|
||||
/// User drag is done.
|
||||
final bool? done;
|
||||
|
||||
/// Same as [TransformerPageView.viewportFraction]
|
||||
final double? viewportFraction;
|
||||
|
||||
/// Copy from [TransformerPageView.scrollDirection]
|
||||
final Axis? scrollDirection;
|
||||
}
|
||||
|
||||
abstract class PageTransformer {
|
||||
PageTransformer({this.reverse = false});
|
||||
|
||||
///
|
||||
final bool reverse;
|
||||
|
||||
/// Return a transformed widget, based on child and TransformInfo
|
||||
Widget transform(Widget child, TransformInfo info);
|
||||
}
|
||||
|
||||
typedef PageTransformerBuilderCallback = Widget Function(
|
||||
Widget child,
|
||||
TransformInfo info,
|
||||
);
|
||||
|
||||
class PageTransformerBuilder extends PageTransformer {
|
||||
PageTransformerBuilder({bool reverse = false, required this.builder})
|
||||
: super(reverse: reverse);
|
||||
|
||||
final PageTransformerBuilderCallback builder;
|
||||
|
||||
@override
|
||||
Widget transform(Widget child, TransformInfo info) {
|
||||
return builder(child, info);
|
||||
}
|
||||
}
|
||||
|
||||
class TransformerPageController extends PageController {
|
||||
TransformerPageController({
|
||||
int initialPage = 0,
|
||||
bool keepPage = true,
|
||||
double viewportFraction = 1.0,
|
||||
this.loop = false,
|
||||
this.itemCount = 0,
|
||||
this.reverse = false,
|
||||
}) : super(
|
||||
initialPage: TransformerPageController._getRealIndexFromRenderIndex(
|
||||
initialPage, loop, itemCount, reverse),
|
||||
keepPage: keepPage,
|
||||
viewportFraction: viewportFraction);
|
||||
|
||||
final bool loop;
|
||||
final int itemCount;
|
||||
final bool reverse;
|
||||
|
||||
int getRenderIndexFromRealIndex(num index) {
|
||||
return _getRenderIndexFromRealIndex(index, loop, itemCount, reverse);
|
||||
}
|
||||
|
||||
int? getRealItemCount() {
|
||||
if (itemCount == 0) return 0;
|
||||
return loop ? itemCount + kMaxValue : itemCount;
|
||||
}
|
||||
|
||||
static int _getRenderIndexFromRealIndex(
|
||||
num index,
|
||||
bool loop,
|
||||
int itemCount,
|
||||
bool reverse,
|
||||
) {
|
||||
if (itemCount == 0) return 0;
|
||||
int renderIndex;
|
||||
if (loop) {
|
||||
renderIndex = (index - kMiddleValue).toInt();
|
||||
renderIndex = renderIndex % itemCount;
|
||||
if (renderIndex < 0) {
|
||||
renderIndex += itemCount;
|
||||
}
|
||||
} else {
|
||||
renderIndex = index.toInt();
|
||||
}
|
||||
if (reverse) {
|
||||
renderIndex = itemCount - renderIndex - 1;
|
||||
}
|
||||
|
||||
return renderIndex;
|
||||
}
|
||||
|
||||
double get realPage => super.page ?? 0.0;
|
||||
|
||||
static double? _getRenderPageFromRealPage(
|
||||
double page,
|
||||
bool loop,
|
||||
int itemCount,
|
||||
bool reverse,
|
||||
) {
|
||||
double? renderPage;
|
||||
if (loop) {
|
||||
renderPage = page - kMiddleValue;
|
||||
renderPage = renderPage % itemCount;
|
||||
if (renderPage < 0) {
|
||||
renderPage += itemCount;
|
||||
}
|
||||
} else {
|
||||
renderPage = page;
|
||||
}
|
||||
if (reverse) {
|
||||
renderPage = itemCount - renderPage - 1;
|
||||
}
|
||||
|
||||
return renderPage;
|
||||
}
|
||||
|
||||
@override
|
||||
double? get page {
|
||||
return loop
|
||||
? _getRenderPageFromRealPage(realPage, loop, itemCount, reverse)
|
||||
: realPage;
|
||||
}
|
||||
|
||||
int getRealIndexFromRenderIndex(num index) {
|
||||
return _getRealIndexFromRenderIndex(index, loop, itemCount, reverse);
|
||||
}
|
||||
|
||||
static int _getRealIndexFromRenderIndex(
|
||||
num index, bool loop, int itemCount, bool reverse) {
|
||||
var result = reverse ? itemCount - index - 1 as int : index as int;
|
||||
if (loop) {
|
||||
result += kMiddleValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TransformerPageView extends StatefulWidget {
|
||||
/// Creates a scrollable list that works page by page using widgets that are
|
||||
/// created on demand.
|
||||
///
|
||||
/// This constructor is appropriate for page views with a large (or infinite)
|
||||
/// number of children because the builder is called only for those children
|
||||
/// that are actually visible.
|
||||
///
|
||||
/// Providing a non-null [itemCount] lets the [PageView] compute the maximum
|
||||
/// scroll extent.
|
||||
///
|
||||
/// [itemBuilder] will be called only with indices greater than or equal to
|
||||
/// zero and less than [itemCount].
|
||||
const TransformerPageView({
|
||||
Key? key,
|
||||
this.index,
|
||||
Duration? duration,
|
||||
this.curve = Curves.ease,
|
||||
this.viewportFraction = 1.0,
|
||||
required this.loop,
|
||||
this.scrollDirection = Axis.horizontal,
|
||||
this.physics,
|
||||
this.pageSnapping = true,
|
||||
this.onPageChanged,
|
||||
this.controller,
|
||||
this.transformer,
|
||||
this.allowImplicitScrolling = false,
|
||||
this.itemBuilder,
|
||||
this.pageController,
|
||||
required this.itemCount,
|
||||
}) : assert(itemCount == 0 || itemBuilder != null || transformer != null),
|
||||
duration = duration ??
|
||||
const Duration(milliseconds: kDefaultTransactionDuration),
|
||||
super(key: key);
|
||||
|
||||
factory TransformerPageView.children({
|
||||
Key? key,
|
||||
int? index,
|
||||
Duration? duration,
|
||||
Curve curve = Curves.ease,
|
||||
double viewportFraction = 1.0,
|
||||
bool loop = false,
|
||||
Axis scrollDirection = Axis.horizontal,
|
||||
ScrollPhysics? physics,
|
||||
bool pageSnapping = true,
|
||||
ValueChanged<int?>? onPageChanged,
|
||||
IndexController? controller,
|
||||
PageTransformer? transformer,
|
||||
bool allowImplicitScrolling = false,
|
||||
required List<Widget> children,
|
||||
TransformerPageController? pageController,
|
||||
}) {
|
||||
return TransformerPageView(
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) {
|
||||
return children[index];
|
||||
},
|
||||
pageController: pageController,
|
||||
transformer: transformer,
|
||||
pageSnapping: pageSnapping,
|
||||
key: key,
|
||||
index: index,
|
||||
loop: loop,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
viewportFraction: viewportFraction,
|
||||
scrollDirection: scrollDirection,
|
||||
physics: physics,
|
||||
allowImplicitScrolling: allowImplicitScrolling,
|
||||
onPageChanged: onPageChanged,
|
||||
controller: controller,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a `transformed` widget base on the widget that has been passed to the [PageTransformer.transform].
|
||||
/// See [TransformInfo]
|
||||
///
|
||||
final PageTransformer? transformer;
|
||||
|
||||
/// Same as [PageView.scrollDirection]
|
||||
///
|
||||
/// Defaults to [Axis.horizontal].
|
||||
final Axis scrollDirection;
|
||||
|
||||
/// Same as [PageView.physics]
|
||||
final ScrollPhysics? physics;
|
||||
|
||||
/// Set to false to disable page snapping, useful for custom scroll behavior.
|
||||
/// Same as [PageView.pageSnapping]
|
||||
final bool pageSnapping;
|
||||
|
||||
/// Called whenever the page in the center of the viewport changes.
|
||||
/// Same as [PageView.onPageChanged]
|
||||
final ValueChanged<int>? onPageChanged;
|
||||
|
||||
final IndexedWidgetBuilder? itemBuilder;
|
||||
|
||||
// See [IndexController.mode],[IndexController.next],[IndexController.previous]
|
||||
final IndexController? controller;
|
||||
|
||||
/// Animation duration
|
||||
final Duration duration;
|
||||
|
||||
/// Animation curve
|
||||
final Curve curve;
|
||||
|
||||
final TransformerPageController? pageController;
|
||||
|
||||
/// Set true to open infinity loop mode.
|
||||
final bool loop;
|
||||
|
||||
/// This value is only valid when `pageController` is not set,
|
||||
final int itemCount;
|
||||
|
||||
/// This value is only valid when `pageController` is not set,
|
||||
final double viewportFraction;
|
||||
|
||||
/// If not set, it is controlled by this widget.
|
||||
final int? index;
|
||||
|
||||
final bool allowImplicitScrolling;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _TransformerPageViewState();
|
||||
|
||||
static int getRealIndexFromRenderIndex({
|
||||
required bool reverse,
|
||||
int index = 0,
|
||||
int itemCount = 0,
|
||||
required bool loop,
|
||||
}) {
|
||||
var initPage = reverse ? (itemCount - index - 1) : index;
|
||||
if (loop) {
|
||||
initPage += kMiddleValue;
|
||||
}
|
||||
return initPage;
|
||||
}
|
||||
|
||||
static PageController createPageController({
|
||||
required bool reverse,
|
||||
int index = 0,
|
||||
int itemCount = 0,
|
||||
required bool loop,
|
||||
required double viewportFraction,
|
||||
}) {
|
||||
return PageController(
|
||||
initialPage: getRealIndexFromRenderIndex(
|
||||
reverse: reverse,
|
||||
index: index,
|
||||
itemCount: itemCount,
|
||||
loop: loop,
|
||||
),
|
||||
viewportFraction: viewportFraction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TransformerPageViewState extends State<TransformerPageView> {
|
||||
Size? _size;
|
||||
int _activeIndex = 0;
|
||||
late double _currentPixels;
|
||||
bool _done = false;
|
||||
|
||||
///This value will not change until user end drag.
|
||||
late int _fromIndex;
|
||||
|
||||
PageTransformer? _transformer;
|
||||
|
||||
late TransformerPageController _pageController;
|
||||
|
||||
Widget _buildItemNormal(BuildContext context, int index) {
|
||||
final renderIndex = _pageController.getRenderIndexFromRealIndex(index);
|
||||
return widget.itemBuilder!(context, renderIndex);
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
return AnimatedBuilder(
|
||||
animation: _pageController,
|
||||
builder: (c, w) {
|
||||
final renderIndex =
|
||||
_pageController.getRenderIndexFromRealIndex(index);
|
||||
final child = widget.itemBuilder?.call(context, renderIndex) ??
|
||||
const SizedBox.shrink();
|
||||
if (_size == null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
double position;
|
||||
|
||||
final page = _pageController.realPage;
|
||||
if (_transformer!.reverse) {
|
||||
position = page - index;
|
||||
} else {
|
||||
position = index - page;
|
||||
}
|
||||
position *= widget.viewportFraction;
|
||||
|
||||
final info = TransformInfo(
|
||||
index: renderIndex,
|
||||
width: _size!.width,
|
||||
height: _size!.height,
|
||||
position: position.clamp(-1.0, 1.0),
|
||||
activeIndex:
|
||||
_pageController.getRenderIndexFromRealIndex(_activeIndex),
|
||||
fromIndex: _fromIndex,
|
||||
forward: _pageController.position.pixels - _currentPixels >= 0,
|
||||
done: _done,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
viewportFraction: widget.viewportFraction,
|
||||
);
|
||||
|
||||
return _transformer!.transform(child, info);
|
||||
});
|
||||
}
|
||||
|
||||
double? _calcCurrentPixels() {
|
||||
_currentPixels = _pageController.getRenderIndexFromRealIndex(_activeIndex) *
|
||||
_pageController.position.viewportDimension *
|
||||
widget.viewportFraction;
|
||||
|
||||
// print("activeIndex:$_activeIndex , pix:$_currentPixels");
|
||||
|
||||
return _currentPixels;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final builder = _transformer == null ? _buildItemNormal : _buildItem;
|
||||
final child = PageView.builder(
|
||||
allowImplicitScrolling: widget.allowImplicitScrolling,
|
||||
itemBuilder: builder,
|
||||
itemCount: _pageController.getRealItemCount(),
|
||||
onPageChanged: _onIndexChanged,
|
||||
controller: _pageController,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
physics: widget.physics,
|
||||
pageSnapping: widget.pageSnapping,
|
||||
reverse: _pageController.reverse,
|
||||
);
|
||||
if (_transformer == null) {
|
||||
return child;
|
||||
}
|
||||
return NotificationListener(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollStartNotification) {
|
||||
_calcCurrentPixels();
|
||||
_done = false;
|
||||
_fromIndex = _activeIndex;
|
||||
} else if (notification is ScrollEndNotification) {
|
||||
_calcCurrentPixels();
|
||||
_fromIndex = _activeIndex;
|
||||
_done = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _onIndexChanged(int index) {
|
||||
_activeIndex = index;
|
||||
widget.onPageChanged
|
||||
?.call(_pageController.getRenderIndexFromRealIndex(index));
|
||||
}
|
||||
|
||||
void _onGetSize(Duration _) {
|
||||
if (!mounted) return;
|
||||
Size? size;
|
||||
|
||||
final renderObject = context.findRenderObject();
|
||||
if (renderObject != null) {
|
||||
final bounds = renderObject.paintBounds;
|
||||
size = bounds.size;
|
||||
}
|
||||
_calcCurrentPixels();
|
||||
onGetSize(size);
|
||||
}
|
||||
|
||||
void onGetSize(Size? size) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_size = size;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IndexController? _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_transformer = widget.transformer;
|
||||
// int index = widget.index ?? 0;
|
||||
_pageController = widget.pageController ??
|
||||
TransformerPageController(
|
||||
initialPage: widget.index ?? 0,
|
||||
itemCount: widget.itemCount,
|
||||
loop: widget.loop,
|
||||
reverse: widget.transformer?.reverse ?? false,
|
||||
);
|
||||
// int initPage = _getRealIndexFromRenderIndex(index);
|
||||
// _pageController = PageController(initialPage: initPage,viewportFraction: widget.viewportFraction);
|
||||
_fromIndex = _activeIndex = _pageController.initialPage;
|
||||
|
||||
_controller = widget.controller;
|
||||
_controller?.addListener(onChangeNotifier);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(TransformerPageView oldWidget) {
|
||||
_transformer = widget.transformer;
|
||||
final index = widget.index ?? 0;
|
||||
var created = false;
|
||||
if (_pageController != widget.pageController) {
|
||||
if (widget.pageController != null) {
|
||||
_pageController = widget.pageController!;
|
||||
} else {
|
||||
created = true;
|
||||
_pageController = TransformerPageController(
|
||||
initialPage: widget.index ?? 0,
|
||||
itemCount: widget.itemCount,
|
||||
loop: widget.loop,
|
||||
reverse: widget.transformer?.reverse ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_pageController.getRenderIndexFromRealIndex(_activeIndex) != index) {
|
||||
_fromIndex = _activeIndex = _pageController.initialPage;
|
||||
if (!created) {
|
||||
final initPage = _pageController.getRealIndexFromRenderIndex(index);
|
||||
if (_pageController.hasClients) {
|
||||
unawaited(_pageController.animateToPage(
|
||||
initPage,
|
||||
duration: widget.duration,
|
||||
curve: widget.curve,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_transformer != null) {
|
||||
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback(_onGetSize);
|
||||
}
|
||||
|
||||
if (_controller != widget.controller) {
|
||||
_controller?.removeListener(onChangeNotifier);
|
||||
_controller = widget.controller;
|
||||
_controller?.addListener(onChangeNotifier);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
if (_transformer != null) {
|
||||
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback(_onGetSize);
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
Future<void> onChangeNotifier() async {
|
||||
final controller = widget.controller!;
|
||||
final event = controller.event;
|
||||
int index;
|
||||
if (event == null) return;
|
||||
if (event is MoveIndexControllerEvent) {
|
||||
index = _pageController.getRealIndexFromRenderIndex(event.newIndex);
|
||||
} else if (event is StepBasedIndexControllerEvent) {
|
||||
index = event.calcNextIndex(
|
||||
currentIndex: _activeIndex,
|
||||
itemCount: _pageController.itemCount,
|
||||
loop: _pageController.loop,
|
||||
reverse: _pageController.reverse,
|
||||
);
|
||||
} else {
|
||||
//ignore other events
|
||||
return;
|
||||
}
|
||||
if (_pageController.hasClients) {
|
||||
if (event.animation) {
|
||||
await _pageController
|
||||
.animateToPage(
|
||||
index,
|
||||
duration: widget.duration,
|
||||
curve: widget.curve,
|
||||
)
|
||||
.whenComplete(event.complete);
|
||||
} else {
|
||||
event.complete();
|
||||
}
|
||||
} else {
|
||||
event.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.removeListener(onChangeNotifier);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Ref: https://docs.flutter.dev/development/tools_base/sdk/release-notes/release-notes-3.0.0#your-code
|
||||
/// This allows a value of type T or T?
|
||||
/// to be treated as a value of type T?.
|
||||
///
|
||||
/// We use this so that APIs that have become
|
||||
/// non-nullable can still be used with `!` and `?`
|
||||
/// to support older versions of the API as well.
|
||||
T? _ambiguate<T>(T? value) => value;
|
||||
Reference in New Issue
Block a user