import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class ImagePoints extends StatefulWidget {
// 图片路径(只接受本地路径或者网络路径)
final String imgUrl;
// 四个点的坐标数组(例 [Offset(100, 100),Offset(200, 100),Offset(200, 200),Offset(100, 200),])
final List<Offset> points;
// 点的宽高
final double pointSize;
// 点的颜色
final Color pointColor;
// 线的颜色
final Color lineColor;
// 线的宽度
final double lineWidth;
// 点移动时的回调,会返回一个参数为新的点位置,顺序为传入points的倒序
Function? listenerPoint;
ImagePoints({
required this.imgUrl,
required this.points,
this.pointSize = 20,
this.pointColor = Colors.green,
this.lineColor = Colors.red,
this.lineWidth = 5,
this.listenerPoint,
});
@override
_ImagePointsState createState() => _ImagePointsState();
}
class _ImagePointsState extends State<ImagePoints> {
@override
Widget build(BuildContext context) {
return FutureBuilder<ui.Image>(
future: loadImage(widget.imgUrl),
builder: (context, snapshot) {
if (snapshot.data != null) {
return CustomPaint(
painter: ImagePainter(snapshot.data!, widget.points,
pointWidth: widget.pointSize,
lineColor: widget.lineColor,
lineWidth: widget.lineWidth),
child: Stack(
children: _pointWidget(draggablePoints: widget.points)));
} else {
return SizedBox();
}
},
);
}
// 拖动角点中心点时,实际移动的还是角点,同时移动两个
Map setDouble(i) {
// 移动的角点下标
List _list = [];
// 移动的是否x轴
bool isX = true;
if (i == -1) {
_list = [0, 1];
isX = true;
} else if (i == -2) {
_list = [1, 2];
isX = false;
} else if (i == -3) {
_list = [2, 3];
isX = true;
} else if (i == -4) {
_list = [3, 0];
isX = false;
}
return {'list': _list, 'isX': isX};
}
// 防止越出屏幕外侧
bool _prevent({required dragga}) {
return dragga.dx <= 0 ||
dragga.dy <= 0 ||
dragga.dx >= MediaQuery.of(context).size.width ||
dragga.dy >= MediaQuery.of(context).size.height;
}
// 实际显示的点(覆盖了画布上绘制的点,因为无法在画布绘制的点上直接添加事件)
List<Widget> _pointWidget({required List<Offset> draggablePoints}) {
var _event;
List<Widget> pointWidget = [
ClipPath(
clipper: MyCustomClipper(draggablePoints),
child: Listener(
// 拖拽点中间位置可挪动整个图框
onPointerMove: (PointerMoveEvent event) {
setState(() {
for (var j = 0; j < draggablePoints.length; j++) {
if (_event != null) {
var _dragga = Offset(
draggablePoints[j].dx +
(event.position.dx - _event.position.dx),
draggablePoints[j].dy +
(event.position.dy - _event.position.dy),
);
if (_prevent(dragga: _dragga)) return;
draggablePoints[j] = _dragga;
}
}
_event = event;
});
if (widget.listenerPoint != null) {
// 将新位置数组倒序抛出
final List<Offset> reversedNumbers =
draggablePoints.reversed.toList();
widget.listenerPoint!(reversedNumbers);
}
},
child: Container(
width: double.infinity,
height: double.infinity,
color: Colors.pink,
),
),
)
];
// 拖动角点之间的点
void _centerPoint({required i, required event, required oldEvent}) {
if (setDouble(i)['isX']) {
setDouble(i)['list'].forEach((e) {
var _dragga = Offset(draggablePoints[e].dx,
draggablePoints[e].dy + (event.position.dy - oldEvent.position.dy));
if (_prevent(dragga: _dragga)) return;
draggablePoints[e] = _dragga;
});
} else {
setDouble(i)['list'].forEach((e) {
var _dragga = Offset(
draggablePoints[e].dx + (event.position.dx - oldEvent.position.dx),
draggablePoints[e].dy);
if (_prevent(dragga: _dragga)) return;
draggablePoints[e] = _dragga;
});
}
}
// 只有角点之间的点才需要传坐标点offset
Widget _widget(i, {Offset? offset}) {
var _oldEvent;
return Transform.translate(
offset: offset ??
Offset(draggablePoints[i].dx - widget.pointSize / 2,
draggablePoints[i].dy - widget.pointSize / 2),
child: Listener(
// 按下并在元素上移动时(拖拽点时)
onPointerMove: (PointerMoveEvent event) {
setState(() {
// 拉动的是角点之间的点
if (i < 0 && _oldEvent != null) {
_centerPoint(event: event, i: i, oldEvent: _oldEvent);
} else if (i >= 0) {
// 拉动角点
draggablePoints[i] = event.position;
}
// 传入的回调
if (widget.listenerPoint != null) {
// 将新位置数组倒序抛出
final List<Offset> reversedNumbers =
draggablePoints.reversed.toList();
widget.listenerPoint!(reversedNumbers);
}
_oldEvent = event;
});
},
child: Container(
width: widget.pointSize,
height: widget.pointSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.pointColor,
),
),
),
);
}
for (var i = 0; i < draggablePoints.length; i++) {
// 添加角点
pointWidget.add(_widget(i));
// 添加角点的中心点
if (i == draggablePoints.length - 1) {
final midPoint = Offset(
(draggablePoints[draggablePoints.length - 1].dx +
draggablePoints[0].dx) /
2 -
widget.pointSize / 2,
(draggablePoints[draggablePoints.length - 1].dy +
draggablePoints[0].dy) /
2 -
widget.pointSize / 2,
);
// 添加最后一个点与第一个点之间的点
pointWidget.add(_widget(-i - 1, offset: midPoint));
} else {
final midPoint = Offset(
(draggablePoints[i].dx + draggablePoints[i + 1].dx) / 2 -
widget.pointSize / 2,
(draggablePoints[i].dy + draggablePoints[i + 1].dy) / 2 -
widget.pointSize / 2,
);
// 添加线段的中间点
pointWidget.add(_widget(-i - 1, offset: midPoint));
}
}
return pointWidget;
}
}
// 绘制点和线
class ImagePainter extends CustomPainter {
// 图片路径
final ui.Image image;
// 点坐标数组
final List<Offset> points;
// 点的宽度
double pointWidth;
// 线颜色
Color lineColor;
// 线宽
double lineWidth;
ImagePainter(
this.image,
this.points, {
required this.pointWidth,
required this.lineColor,
required this.lineWidth,
});
// 在此进行实际绘图操作
@override
void paint(Canvas canvas, Size size) {
// 获取画布大小
final imageSize = Size(image.width.toDouble(), image.height.toDouble());
final fitSize = calculateFitSize(imageSize, size);
final fitRect = Alignment.center.inscribe(fitSize, Offset.zero & size);
canvas.drawImageRect(image, Offset.zero & imageSize, fitRect, Paint());
// 点的样式
final pointStyle = Paint()
..color = Colors.transparent
..strokeWidth = pointWidth
..strokeCap = StrokeCap.round;
// 线的样式
final lineStyle = Paint()
..color = lineColor
..strokeWidth = lineWidth
..strokeCap = StrokeCap.round;
for (var i = 0; i < points.length; i++) {
// 绘制初始点
canvas.drawPoints(PointMode.points, [points[i]], pointStyle);
if (i == points.length - 1) {
final midPoint = Offset(
(points[points.length - 1].dx + points[0].dx) / 2,
(points[points.length - 1].dy + points[0].dy) / 2,
);
// 将最后一个点与第一个点连成线
canvas.drawLine(points[points.length - 1], points[0], lineStyle);
// 添加最后一个点与第一个点之间的点
canvas.drawCircle(midPoint, pointWidth / 2, pointStyle);
} else {
// 连接线
canvas.drawLine(points[i], points[i + 1], lineStyle);
// 添加线段的中间点
final midPoint = Offset(
(points[i].dx + points[i + 1].dx) / 2,
(points[i].dy + points[i + 1].dy) / 2,
);
// 添加中间点
canvas.drawCircle(midPoint, pointWidth / 2, pointStyle);
}
}
}
// 是否需要重新绘制
@override
bool shouldRepaint(ImagePainter oldDelegate) {
return image != oldDelegate.image || points != oldDelegate.points;
}
}
// 加载图片
Future<ui.Image> loadImage(String imgUrl) async {
final ImageProvider _imageProvider;
// 是否是网络图片
bool containsHttp = imgUrl.contains("http");
if (containsHttp) {
_imageProvider = NetworkImage(imgUrl);
} else {
_imageProvider = FileImage(File(imgUrl));
}
final completer = Completer<ui.Image>();
final stream = _imageProvider.resolve(ImageConfiguration.empty);
stream.addListener(ImageStreamListener((info, _) {
completer.complete(info.image);
}));
final image = await completer.future;
return image;
}
// 将图像调整到画布区域的大小
Size calculateFitSize(Size imageSize, Size destinationSize) {
final srcWidth = imageSize.width;
final srcHeight = imageSize.height;
final destWidth = destinationSize.width;
final destHeight = destinationSize.height;
final srcRatio = srcWidth / srcHeight;
final destRatio = destWidth / destHeight;
double width;
double height;
if (destRatio > srcRatio) {
width = srcWidth * destHeight / srcHeight;
height = destHeight;
} else {
width = destWidth;
height = srcHeight * destWidth / srcWidth;
}
return Size(width, height);
}
// 由角点绘制成面
class MyCustomClipper extends CustomClipper<Path> {
List points;
MyCustomClipper(this.points);
@override
Path getClip(Size size) {
final path = Path();
path.moveTo(points[0].dx, points[0].dy); // 左上角坐标
path.lineTo(points[1].dx, points[1].dy); // 右上角坐标
path.lineTo(points[2].dx, points[2].dy); // 右下角坐标
path.lineTo(points[3].dx, points[3].dy); // 左下角坐标
path.close(); // 闭合路径
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
Flutter 仿图片裁剪框
猜你喜欢
转载自blog.csdn.net/weixin_48235660/article/details/133179237
今日推荐
周排行