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;
  }
}
相关推荐
xfhuangfu12 分钟前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT24 分钟前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData36 分钟前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean1 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean1 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定
浪子明X2 小时前
从 MongoDB 文档到关系模型:构建可重跑、可对账的数据迁移流水线
数据库·mongodb·oracle
杜子不疼.2 小时前
国产数据库撑起固井软件自主化:金仓 × 中海油服「海恒 Cemsol」落地解析
数据库
隔窗听雨眠2 小时前
GBase 8s并发控制深度解析:封锁机制、隔离级别与死锁处理全攻略
服务器·数据库·oracle
刀客Doc2 小时前
即时零售不只是多一个渠道,品牌开始重做终端生意
大数据·数据库
就叫飞六吧3 小时前
防抖、幂等、唯一约束三件套 ;
数据库