一、先放上一张静态图
可拖拽二阶贝塞尔曲线.png
可以拖动起点,控制点和终点查看二阶贝塞尔曲线。
二、实现思路
二阶贝塞尔曲线就是三个点,所以当点击不超过三个点的时候只需要绘制点。否则就绘制二阶贝塞尔曲线和点的连线。当拖动其中一个点的时候重新绘制。所以重点是如何触发重绘。
画板的重绘需要一个Listenable对象。ChangeNotifier是一个实现了Lisenable的类,在数据变动的时候可以通过notifyListeners()来刷新界面。
三、代码实现
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/canvas/canvas.dart';
import 'package:flutter_app/path/path.dart';
class Path2 extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return Path2State();
}
}
class Path2State extends State<Path2> {
TouchInfo touchInfo = TouchInfo();
@override
void dispose() {
super.dispose();
touchInfo.dispose();
}
@override
void initState() {
super.initState();
initPoints();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: GestureDetector(
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
child: CustomPaint(
painter: Path2CustomPainter(repaint: touchInfo),
),
),
);
}
void _onPanDown(DragDownDetails details) {
if (touchInfo.points.length < 3) {
touchInfo.addPoint(details.localPosition);
} else {
///绘制曲线
judgeZone(details.localPosition);
}
}
void _onPanUpdate(DragUpdateDetails details) {
judgeZone(details.localPosition, update: true);
}
///判断是否在某点半径范围内
bool judgeCircleArea(Offset src, Offset dst, double r) =>
(src - dst).distance <= r;
///判断那个点被选中
void judgeZone(Offset src, {bool update = false}) {
for (int i = 0; i < touchInfo.points.length; i++) {
if (judgeCircleArea(src, touchInfo.points[i], 15)) {
touchInfo.selectIndex = i;
if (update) {
touchInfo.updatePoint(i, src);
}
}
}
}
}
class TouchInfo extends ChangeNotifier {
List<Offset> _points = [];
int _selectIndex = -1;
int get selectIndex => _selectIndex;
List<Offset> get points => _points;
set selectIndex(int value) {
assert(value != null);
if (_selectIndex == value) return;
_selectIndex = value;
notifyListeners();
}
void addPoint(Offset offset) {
points.add(offset);
notifyListeners();
}
void updatePoint(int index, Offset point) {
points[index] = point;
notifyListeners();
}
Offset get selectPoint => _selectIndex == -1 ? null : _points[_selectIndex];
}
class Path2CustomPainter extends CustomPainter {
final TouchInfo repaint;
List<Offset> pos;
Path2CustomPainter({this.repaint}):super(repaint:repaint);
@override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
// path(canvas);
drawCoordinate(canvas, size);
drawGirdLine(canvas, size);
//// quadraticBezierTo(canvas);
///因为画布平移了,所以三个点也需要平移
pos = repaint.points.map((e) => e.translate(-size.width / 2,- size.height / 2)).toList();
///如果点数少于三个就绘制点 如果大于三个就绘制贝塞尔曲线,绘制辅助线
if(pos.length < 3){
canvas.drawPoints(PointMode.points, pos, Paint()..color = Colors.purple
..strokeWidth = 8..strokeCap = StrokeCap.round);
}else{
Path path = Path();
path.moveTo(pos[0].dx, pos[0].dy);
path.quadraticBezierTo(pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy);
canvas.drawPath(path, Paint()..color = Colors.purple..style = PaintingStyle.stroke
..strokeWidth = 2);
///画线
canvas.drawPoints(PointMode.points, pos, Paint()..color = Colors.purple
..strokeWidth = 8..strokeCap = StrokeCap.round);
canvas.drawPoints(PointMode.polygon, pos, Paint()..color = Colors.purple);
}
}
网友评论