- 演示
- 功能
1.可自定义设置宽高
2.可点击回调
3.可设置是否自动轮播
4.可设置指示器,抽象类IIndicator,IWidgetIndicator,IDrawIndicator
- 代码
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class KqSwiper<T extends IShowData> extends StatefulWidget {
/// 展示的数据
final List<T> showData;
/// 点击回调
final Function(int index, T data)? onTap;
/// 界面宽度
final double width;
/// 界面高度
final double height;
/// 是否开启自动滑动
final bool isAutoSlide;
///指示器
final IIndicator? indicator;
const KqSwiper(
{super.key,
required this.showData,
this.onTap,
this.width = 360,
this.height = 250,
this.isAutoSlide = true,
this.indicator});
@override
State<StatefulWidget> createState() => _KqSwiperState<T>();
}
class _KqSwiperState<T extends IShowData> extends State<KqSwiper<T>> {
/// 执行自动滑动的时间间隔s
static const int slideTime = 6;
/// 图片数据
final List<ui.Image> images = [];
/// 当前正在显示的图片
int index = 0;
/// 前一张图片
ui.Image? _preImage;
/// 当前图片
ui.Image? _curImage;
/// 下一张图片
ui.Image? _nextImage;
/// 滑动偏移
double _offset = 0;
/// 执行滑动动画计时器
Timer? timer;
///定时滑动计时器
Timer? timer2;
/// 指示器
IIndicator? _indicator;
/// 当Indicator的类型为widget时的widget列表值
List<Widget>? _indicatorWidgetList;
@override
void initState() {
_initImage();
_indicator = widget.indicator ?? DrawIndicator();
super.initState();
}
void _initImage() async {
for (T data in widget.showData) {
images.add(await getImage(data,
width: widget.width.toInt(), height: widget.height.toInt()));
}
_showImage();
_startSlide();
}
void _showImage() {
if (index == 0) {
_preImage = images[widget.showData.length - 1];
} else {
_preImage = images[index - 1];
}
_curImage = images[index];
if (index == widget.showData.length - 1) {
_nextImage = images[0];
} else {
_nextImage = images[index + 1];
}
_initWidgetIndicator();
setState(() {});
}
Future<ui.Image> getImage(T data, {width, height}) async {
ui.Image image;
if (data.type == FileSourceType.network) {
image = await getNetImage(data.path, width: width, height: height);
} else if (data.type == FileSourceType.asset) {
image = await getAssetImage(data.path, width: width, height: height);
} else {
image = await getNetImage(data.path, width: width, height: height);
}
return image;
}
void _animateTo(bool isSwiper, double hasSwiperWidth) {
_initWidgetIndicator();
double showCount = 0;
if (isSwiper) {
showCount = widget.width - hasSwiperWidth;
} else {
showCount = hasSwiperWidth;
}
//计时器,每1毫秒执行一次
const period = Duration(milliseconds: 1);
timer = Timer.periodic(period, (timer) {
showCount--;
if (_offset > 0) {
if (isSwiper) {
_offset++;
} else {
_offset--;
}
} else {
if (isSwiper) {
_offset--;
} else {
_offset++;
}
}
//计时器结束条件
if (showCount.toInt() == 0) {
timer.cancel();
_offset = 0;
_showImage();
_startSlide();
}
//只有当widget状态为mounted时才执行setState防止内存泄露
if (mounted) {
setState(() {});
}
});
}
void _initWidgetIndicator(){
if (_indicator?.type == IndicatorType.widget) {
_indicatorWidgetList =
_indicator?.onIWidget(widget.showData.length, index);
}
}
void _startSlide() {
if (widget.isAutoSlide) {
if (timer2 == null || !timer2!.isActive) {
//计时器,每1毫秒执行一次
const period = Duration(seconds: slideTime);
timer2 = Timer.periodic(period, (timer) {
if (index == widget.showData.length - 1) {
index = 0;
} else {
index++;
}
_animateTo(true, 0);
});
}
}
}
@override
void dispose() {
//退出时关闭计时器防止内存泄露
timer?.cancel();
timer2?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: widget.height,
width: widget.width,
child: RepaintBoundary(
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: [
GestureDetector(
child: ClipRect(
child: CustomPaint(
painter: _SwiperPinter(this, widget.showData.length, index,
_preImage, _curImage, _nextImage, _offset, _indicator!),
)),
onTap: () {
widget.onTap?.call(index, widget.showData[index]);
},
onHorizontalDragDown: (DragDownDetails details) {
timer2?.cancel();
},
onHorizontalDragUpdate: (DragUpdateDetails details) {
_offset += details.delta.dx;
setState(() {});
},
onHorizontalDragEnd: (DragEndDetails details) {
bool isSwiper = false;
if (_offset.abs() >= widget.width / 3) {
if (_offset < 0) {
if (index == widget.showData.length - 1) {
index = 0;
} else {
index++;
}
} else {
if (index == 0) {
index = widget.showData.length - 1;
} else {
index--;
}
}
isSwiper = true;
}
_animateTo(isSwiper, _offset.abs());
},
),
Positioned(
bottom: 10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _indicatorWidgetList ?? [])),
])),
);
}
}
class _SwiperPinter extends CustomPainter {
final _KqSwiperState state;
final int _length;
final int _index;
final ui.Image? _preImage;
final ui.Image? _curImage;
final ui.Image? _nextImage;
final double _offset;
final IIndicator _indicator;
Paint selfPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill
..isAntiAlias = true
..strokeCap = StrokeCap.butt
..strokeWidth = 30.0;
_SwiperPinter(this.state, this._length, this._index, this._preImage,
this._curImage, this._nextImage, this._offset, this._indicator);
@override
void paint(Canvas canvas, Size size) {
if (_preImage == null || _curImage == null || _nextImage == null) {
return;
}
canvas.drawImage(
_preImage!, Offset(-state.widget.width + _offset, 0), selfPaint);
canvas.drawImage(_curImage!, Offset(0 + _offset, 0), selfPaint);
canvas.drawImage(
_nextImage!, Offset(state.widget.width + _offset, 0), selfPaint);
if (_indicator.type == IndicatorType.draw) {
_indicator.onIDraw(
canvas, _length, _index, state.widget.width, state.widget.height);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
/// 文件源类型
enum FileSourceType {
/// 网络文件
network,
/// 系统文件
asset
}
/// 文件格式
abstract class IShowData {
/// 展示的图片路径
final String path;
/// 图片路径位置
final FileSourceType type;
IShowData(this.path, this.type);
}
//获取网络图片 返回ui.Image
Future<ui.Image> getNetImage(String url, {width, height}) async {
ByteData data = await NetworkAssetBundle(Uri.parse(url)).load(url);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: width, targetHeight: height);
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
//获取本地图片,返回ui.Image
Future<ui.Image> getAssetImage(String asset, {width, height}) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
targetWidth: width, targetHeight: height);
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
enum IndicatorType {
/// widget类型
widget,
/// 绘制类型
draw
}
/// 指示器,实现该类进行自定义绘制
abstract class IIndicator {
final IndicatorType type;
IIndicator(this.type);
/// 自定义绘制
/// length 指示器个数
/// curIndex 指示器当前位置
/// w 控件宽度
/// h 控件高度
onIDraw(Canvas canvas, int length, int curIndex, double w, double h);
/// 自定义传widget
/// length 指示器个数
/// curIndex 指示器当前位置
/// return List<Widget> 指示器列表
List<Widget> onIWidget(int length, int curIndex);
}
/// 传widget的指示器抽象类,传widget的方式需要继承该类实现
abstract class IWidgetIndicator extends IIndicator {
IWidgetIndicator() : super(IndicatorType.widget);
@override
onIDraw(Canvas canvas, int length, int curIndex, double w, double h) {
///啥也不干
}
@override
List<Widget> onIWidget(int length, int curIndex) {
return onWidget(length, curIndex);
}
/// 过渡方法
List<Widget> onWidget(int length, int curIndex);
}
/// 自定义绘制的指示器抽象类,自定义绘制的方式需要继承该类实现
abstract class IDrawIndicator extends IIndicator {
IDrawIndicator() : super(IndicatorType.draw);
@override
onIDraw(Canvas canvas, int length, int curIndex, double w, double h) {
onDraw(canvas, length, curIndex, w, h);
}
@override
List<Widget> onIWidget(int length, int curIndex) {
///啥也不干
return [];
}
/// 过渡方法
onDraw(Canvas canvas, int length, int curIndex, double w, double h);
}
/// 内置绘制指示器
class DrawIndicator extends IDrawIndicator {
/// 指示器单个点x方向偏移
final double _dotOffsetDx = 10;
/// 指示器单个点y方向偏移
final double _dotOffsetDy = 10;
/// 指示器点的半径
final double _dotRadius = 4;
/// 指示器画笔
final Paint _paint = Paint()
..style = PaintingStyle.fill
..isAntiAlias = true
..strokeCap = StrokeCap.butt
..strokeWidth = 30.0;
@override
onDraw(Canvas canvas, int length, int curIndex, double w, double h) {
for (int i = 0; i < length; i++) {
//绘制当前指示器
if (i == curIndex) {
_paint.color = Colors.blue;
canvas.drawCircle(
Offset(
(w / 2 - (_dotRadius * 2 + (length - 1) * _dotOffsetDx) / 2) +
(_dotRadius * 2 + _dotOffsetDx) * i,
h - _dotOffsetDy),
_dotRadius,
_paint);
// 绘制非当前的其它指示器
} else {
_paint.color = Colors.grey;
canvas.drawCircle(
Offset(
(w / 2 - (_dotRadius * 2 + (length - 1) * _dotOffsetDx) / 2) +
(_dotRadius * 2 + _dotOffsetDx) * i,
h - _dotOffsetDy),
_dotRadius,
_paint);
}
}
}
}
/// 内置widget指示器
class WidgetIndicator extends IWidgetIndicator {
@override
List<Widget> onWidget(int length, int curIndex) {
List<Widget> widgets = [];
for (int i = 0; i < length; i++) {
if (curIndex == i) {
widgets.add(Text(
(i + 1).toString(),
style:
const TextStyle(color: Colors.red, backgroundColor: Colors.white),
));
} else {
widgets.add(Text(
(i + 1).toString(),
style: const TextStyle(
color: Colors.black, backgroundColor: Colors.white),
));
}
}
return widgets;
}
}
代码可直接运行使用,代码量少,逻辑简单,二次开发方便。
- 使用
构建数据:
class TestSwiperData extends IShowData {
TestSwiperData(super.path, super.type);
}
List<TestSwiperData> imgList = [
TestSwiperData(
"https://alifei05.cfp.cn/creative/vcg/800/version23/VCG41175510742.jpg",
FileSourceType.network),
TestSwiperData(
"https://tenfei04.cfp.cn/creative/vcg/veer/1600water/veer-147317368.jpg",
FileSourceType.network),
TestSwiperData(
"assets/images/common/ic_xinzeng_16_blue.png", FileSourceType.asset),
];
使用:
KqSwiper<TestSwiperData>(
width: 414.w,
showData: imgList,
indicator: WidgetIndicator(),
onTap: (index,data){
KqToast.showNormal("index=$index;image=${data.path}");
},
)