效果图

实现逻辑
Dart
1.二维列表存储多笔画,一维列表存储当前笔画
2.Listener手势监听
3.创建绘画类
数据存储结构
Dart
// 二维列表:存储多笔画
List<List<Offset>> strokes = []; // 所有完成的笔画
// 例如:strokes = [
// [p1, p2, p3, ...], // 第一笔
// [p4, p5, p6, ...], // 第二笔
// [p7, p8, p9, ...] // 第三笔
// ]
// 一维列表:存储当前笔画
List<Offset>? currentStroke; // 正在画的笔画
Listener手势监听
Dart
Listener(
onPointerDown: (event) { ... }, // 手指按下
onPointerMove: (event) { ... }, // 手指移动
onPointerUp: (event) { ... }, // 手指抬起
)
完整的状态变化示例
Dart
// 初始状态
strokes = [] // 空画板
currentStroke = null // 没有正在画的笔画
// 第一笔:按下
currentStroke = [p1] // 记录起点
// 第一笔:移动
currentStroke = [p1, p2] // 添加点
currentStroke = [p1, p2, p3] // 继续添加
// 第一笔:抬起
strokes = [[p1, p2, p3]] // 保存到历史
currentStroke = null // 清空当前
// 第二笔:按下
currentStroke = [p4] // 新的一笔
// 最终状态
strokes = [ // 两笔画
[p1, p2, p3], // 第一笔
[p4, p5, p6] // 第二笔
]
绘制原理图解
Dart
点列表:[p1, p2, p3, p4, p5]
绘制过程:
p1 ------ p2 ------ p3 ------ p4 ------ p5
① ② ③ ④
画线①:p1到p2
画线②:p2到p3
画线③:p3到p4
画线④:p4到p5
执行流程
Dart
用户触摸屏幕 → onPointerDown 触发
创建新笔画 → 初始化 currentStroke
手指移动 → onPointerMove 连续触发,添加点
实时显示 → 每次 setState 触发 CustomPaint 重绘
手指抬起 → onPointerUp 触发,保存笔画
点击清除 → 清空所有数据,重置画板
代码实例
Dart
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// 存储多个笔画,每个笔画是一个点列表
List<List<Offset>> strokes = [];
// 当前正在绘制的笔画
List<Offset>? currentStroke;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('画板'),
actions: [
//清除按钮
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
strokes.clear();
currentStroke = null;
});
},
),
],
),
body: Center(
child: Listener(
onPointerDown: (event) {
setState(() {
// 开始新的一笔
currentStroke = [event.localPosition];
});
},
onPointerMove: (event) {
setState(() {
// 添加到当前笔画
currentStroke?.add(event.localPosition);
});
},
onPointerUp: (event) {
setState(() {
// 完成当前笔画,添加到笔画列表
if (currentStroke != null && currentStroke!.length > 1) {
strokes.add(currentStroke!);
}
currentStroke = null;
});
},
child: Container( //画板
width: 300,
height: 300,
color: Colors.grey[200],
child: CustomPaint(
painter: DrawingPainter(
strokes: strokes,
currentStroke: currentStroke,
),
),
),
),
),
);
}
}
class DrawingPainter extends CustomPainter {
final List<List<Offset>> strokes; // 所有完成的笔画
final List<Offset>? currentStroke; // 当前正在画的笔画
DrawingPainter({required this.strokes, this.currentStroke});
@override
void paint(Canvas canvas, Size size) {
//配置画笔
final paint = Paint()
..color = Colors.black
..strokeWidth = 3.0
..strokeCap = StrokeCap.round //圆头端点
..style = PaintingStyle.stroke;
// 绘制所有已完成的笔画
for (var stroke in strokes) {
_drawStroke(canvas, stroke, paint);
}
// 绘制当前笔画(如果有)
if (currentStroke != null) {
_drawStroke(canvas, currentStroke!, paint);
}
}
void _drawStroke(Canvas canvas, List<Offset> stroke, Paint paint) {
if (stroke.isEmpty) return;
//情况1:一个点,画一个点
if (stroke.length == 1) {
canvas.drawCircle(stroke.first, 2, paint); //参数:圆心位置、圆半径、画笔样式
return;
}
// 情况2:绘制线条
for (int i = 0; i < stroke.length - 1; i++) { //把笔画中的相邻点两两连接起来,形成连续的线条
canvas.drawLine(stroke[i], stroke[i + 1], paint);//参数:点1,点2,画笔样式
}
}
@override
bool shouldRepaint(covariant DrawingPainter oldDelegate) {
return oldDelegate.strokes != strokes ||
oldDelegate.currentStroke != currentStroke;
}
}