Flutter---简单画板应用

效果图

实现逻辑

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;
  }
}
相关推荐
Agent手记13 小时前
安全生产巡检全流程自动化与隐患预警方案:2026工业Agent落地实战指南
数据库·人工智能·安全·ai·自动化
山屿落星辰13 小时前
Flutter 企业级架构设计实战:Clean Architecture + 分层模块化 + 依赖注入全解析
flutter
whn197713 小时前
查询日期报错,参数DATETIME_FMT_MODE
数据库·sql
运维行者_14 小时前
云计算连接性与互操作性
服务器·开发语言·网络·web安全·网络基础设施
红茶要加冰14 小时前
linux的例行性工作——计划任务
linux·运维·服务器
darkdragonking14 小时前
由一次构建 OpenEuler 22.03 dnf源所了解到的
linux·运维·服务器
Gauss松鼠会14 小时前
GaussDB(DWS) GUC参数修改、查看
java·数据库·sql·数据库开发·gaussdb
米高梅狮子14 小时前
Ceph 分布式存储 部署
linux·运维·数据库·分布式·ceph·docker·华为云
滴滴答答哒14 小时前
.NET Core 基于 AOP + Polly 实现数据库死锁自动重试
数据库·.netcore·sqlsugar
WUYOUGYLU14 小时前
云服务器怎么选、怎么用,才不花冤枉钱
运维·服务器