Используйте эти классы, это прекрасно работает.
Модель карусели:
class Carousel {
final List<dynamic> images;
final int currentIndex;
final dynamic defaultImage;
final Curve animationCurve;
final Duration animationDuration;
final double dotSize;
final double dotIncreaseSize;
final double dotSpacing;
final Color dotColor;
final Color dotBgColor;
final Color dotIncreasedColor;
final double indicatorBgPadding;
final BoxFit boxFit;
final double dotHorizontalPadding;
final double dotVerticalPadding;
final double moveIndicatorFromBottom;
final bool noRadiusForIndicator;
final void Function(int, int) onImageChange;
final void Function(int) onImageTap;
Carousel(
this.images,
this.currentIndex,
this.defaultImage,
this.animationCurve,
this.animationDuration,
this.dotSize,
this.dotIncreaseSize,
this.dotSpacing,
this.dotColor,
this.dotBgColor,
this.dotIncreasedColor,
this.indicatorBgPadding,
this.boxFit,
this.dotHorizontalPadding,
this.dotVerticalPadding,
this.moveIndicatorFromBottom,
this.noRadiusForIndicator,
this.onImageChange,
this.onImageTap,
);
}
Виджет карусели:
class ImageCarousel extends StatefulWidget implements Carousel {
@override
final Curve animationCurve;
@override
final Duration animationDuration;
@override
final BoxFit boxFit;
@override
final int currentIndex;
@override
final dynamic defaultImage;
@override
final Color dotBgColor;
@override
final Color dotColor;
@override
final double dotHorizontalPadding;
@override
final double dotIncreaseSize;
@override
final Color dotIncreasedColor;
@override
final double dotSize;
@override
final double dotSpacing;
@override
final double dotVerticalPadding;
@override
final List<dynamic> images;
@override
final double indicatorBgPadding;
@override
final double moveIndicatorFromBottom;
@override
final bool noRadiusForIndicator;
@override
final Function(int, int) onImageChange;
@override
final Function(int) onImageTap;
const ImageCarousel({
this.images,
this.currentIndex = 0,
this.animationCurve = Curves.ease,
this.animationDuration = const Duration(milliseconds: 300),
this.dotSize = 8.0,
this.dotSpacing = 25.0,
this.dotIncreaseSize = 2.0,
this.dotColor = Colors.white,
this.dotBgColor,
this.dotIncreasedColor = Colors.white,
this.indicatorBgPadding = 20.0,
this.boxFit = BoxFit.cover,
this.dotHorizontalPadding = 0.0,
this.dotVerticalPadding = 0.0,
this.moveIndicatorFromBottom = 0.0,
this.noRadiusForIndicator = false,
this.onImageChange,
this.defaultImage,
this.onImageTap,
});
@override
State createState() => ImageCarouselState();
}
class ImageCarouselState extends State<ImageCarousel> {
PageController _controller;
int _currentImageIndex = 0;
Timer timer;
@override
void initState() {
_controller = PageController(initialPage: widget.currentIndex);
if (widget.images != null && widget.images.isNotEmpty) {
timer = Timer.periodic(const Duration(seconds: 7), (_) {
if (_controller.hasClients) {
if (_controller.page.round() == widget.images.length - 1) {
_controller.animateToPage(
0,
duration: widget.animationDuration,
curve: widget.animationCurve,
);
} else {
_controller.nextPage(
duration: widget.animationDuration,
curve: widget.animationCurve,
);
}
}
});
}
super.initState();
}
@override
void dispose() {
_controller.dispose();
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final List<Widget> listImages =
(widget.images != null && widget.images.isNotEmpty)
? _images()
: _defaultImages();
return Stack(
children: <Widget>[
_pages(listImages),
widget.images.length > 1 ? _dots(listImages.length) : Container(),
],
);
}
List<Widget> _images() => widget.images.map<Widget>(
(dynamic netImage) {
return netImage;
},
).toList();
List<Widget> _defaultImages() => <Widget>[
widget.defaultImage,
];
Widget _pages(List<Widget> images) {
final PageView pageView = PageView(
physics: const BouncingScrollPhysics(),
controller: _controller,
children: images,
onPageChanged: (int currentPage) {
if (widget.onImageChange != null) {
widget.onImageChange(_currentImageIndex, currentPage);
}
_currentImageIndex = currentPage;
},
);
if (widget.onImageTap == null) {
return pageView;
}
return GestureDetector(
child: pageView,
onTap: () => widget.onImageTap(_currentImageIndex),
);
}
Widget _dots(int length) => Positioned(
bottom: widget.dotVerticalPadding,
left: 0,
right: 0,
child: DotsIndicator(
controller: _controller,
itemCount: length,
color: widget.dotColor,
increasedColor: widget.dotIncreasedColor,
dotSize: widget.dotSize,
dotSpacing: widget.dotSpacing,
dotIncreaseSize: widget.dotIncreaseSize,
onPageSelected: (int page) {
_controller.animateToPage(
page,
duration: widget.animationDuration,
curve: widget.animationCurve,
);
},
),
);
}
и индикатор:
class DotsIndicator extends AnimatedWidget {
const DotsIndicator({
this.controller,
this.itemCount,
this.onPageSelected,
this.color,
this.increasedColor,
this.dotSize,
this.dotIncreaseSize,
this.dotSpacing,
}) : super(listenable: controller);
final PageController controller;
final int itemCount;
final ValueChanged<int> onPageSelected;
final Color color;
final Color increasedColor;
final double dotSize;
final double dotIncreaseSize;
final double dotSpacing;
Widget _buildDot(int index) {
final double selectedness = Curves.easeOut.transform(
max(
0.0,
1.0 - ((controller.page ?? controller.initialPage) - index).abs(),
),
);
final double zoom = 1.0 + (dotIncreaseSize - 1.0) * selectedness;
final Color dotColor = zoom > 1.0 ? increasedColor : color;
return Container(
width: dotSpacing,
child: Center(
child: Material(
color: dotColor,
type: MaterialType.circle,
child: Container(
width: dotSize * zoom,
height: dotSize * zoom,
child: InkWell(
onTap: () => onPageSelected(index),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List<Widget>.generate(itemCount, _buildDot),
);
}
}
Создать карусель:
Container(
height: ScreenUtil().height(370),
child: ImageCarousel(
boxFit: BoxFit.cover,
images: state.images
.map(
(MainImage image) => CachedNetworkImage(
height: double.infinity,
width: double.infinity,
imageUrl: image.imageUrl,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
),
)
.toList(),
animationCurve: Curves.fastOutSlowIn,
animationDuration: const Duration(seconds: 1),
dotSize: 8,
dotIncreaseSize: 1.01,
dotSpacing: 16,
dotVerticalPadding: ScreenUtil().height(50),
dotIncreasedColor: AppColors.colorButtonGreenLight,
dotBgColor: AppColors.transparent,
onImageTap: (int index) => _onImageTap(context, state.images, index),
),
);