一、先放上一張靜態(tài)圖

可拖拽二階貝塞爾曲線.png
可以拖動起點(diǎn),控制點(diǎn)和終點(diǎn)查看二階貝塞爾曲線。
二、實(shí)現(xiàn)思路
二階貝塞爾曲線就是三個點(diǎn),所以當(dāng)點(diǎn)擊不超過三個點(diǎn)的時候只需要繪制點(diǎn)。否則就繪制二階貝塞爾曲線和點(diǎn)的連線。當(dāng)拖動其中一個點(diǎn)的時候重新繪制。所以重點(diǎn)是如何觸發(fā)重繪。
畫板的重繪需要一個Listenable對象。ChangeNotifier是一個實(shí)現(xiàn)了Lisenable的類,在數(shù)據(jù)變動的時候可以通過notifyListeners()來刷新界面。
三、代碼實(shí)現(xiàn)
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);
}
///判斷是否在某點(diǎn)半徑范圍內(nèi)
bool judgeCircleArea(Offset src, Offset dst, double r) =>
(src - dst).distance <= r;
///判斷那個點(diǎn)被選中
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);
///因?yàn)楫嫴计揭屏?,所以三個點(diǎn)也需要平移
pos = repaint.points.map((e) => e.translate(-size.width / 2,- size.height / 2)).toList();
///如果點(diǎn)數(shù)少于三個就繪制點(diǎn) 如果大于三個就繪制貝塞爾曲線,繪制輔助線
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);
}
}