• 手绘板的制作——重置与橡皮擦(2)


    前言

    在上一篇文章「手绘板的制作——手绘(1) 」中,我们完成了手绘的功能,这一篇我们在其基础上来讲讲重置与橡皮擦的功能实现。

    在讲具体的功能实现前,我们需要先弄几个文本,用于笔刷、重置、橡皮擦的功能切换,同时把手绘板的功能抽取出来,放到 HandPaintedBoard 类中,大致代码如下,由于大多都是 UI 代码,可以直接初略看看即可,若需要自己实践再进行 copy。

    void main() {
      runApp(const MyHomePage());
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      State createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State {
      final PaintedBoardProvider _paintedBoardProvider = PaintedBoardProvider();   // <- 重点在这里
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Container(
              color: Colors.white,
              child: SafeArea(
                child: Flex(
                  direction: Axis.vertical,
                  children: [
                    SizedBox(
                      height: 100,
                      child: Flex(
                        direction: Axis.horizontal,
                        children: [
                          Expanded(
                            child: GestureDetector(
                              onTap: () {
                                print("点击了笔刷");
                              },
                              child: const Center(
                                child: Text("笔刷"),
                              ),
                            ),
                          ),
                          Expanded(
                            child: GestureDetector(
                              onTap: () {
                                print("点击了重置");
                              },
                              child: const Center(
                                child: Text("重置"),
                              ),
                            ),
                          ),
                          Expanded(
                            child: GestureDetector(
                              onTap: () {
                                print("点击了橡皮擦");
                              },
                              child: const Center(
                                child: Text("橡皮擦"),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    Expanded(child: HandPaintedBoard(_paintedBoardProvider)),  // <- 重点在这里
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    class HandPaintedBoard extends StatefulWidget {
      const HandPaintedBoard(
        this._paintedBoardProvider, {
        Key? key,
      }) : super(key: key);
      final PaintedBoardProvider _paintedBoardProvider;
    
      @override
      _HandPaintedBoardState createState() => _HandPaintedBoardState();
    }
    
    class _HandPaintedBoardState extends State {
      PaintedBoardProvider get _paintedBoardProvider =>
          widget._paintedBoardProvider;
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onPanStart: (details) {
            _paintedBoardProvider.onStart(details);
          },
          onPanUpdate: (details) {
            _paintedBoardProvider.onUpdate(details);
          },
          onPanEnd: (details) {
            print("onPanDown:移动结束");
          },
          child: CustomPaint(
            painter: MyPainter(_paintedBoardProvider),
            size: Size.infinite,
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    至于 stroke.dartpainted_board_provider.dartmy_painter.dart「手绘板的制作——手绘(1) 」中都有,这里就不贴出了。

    UI 效果:

    在这里插入图片描述

    然后我们点击三个按钮试试:

    I/flutter: 点击了笔刷
    I/flutter: 点击了重置
    I/flutter: 点击了橡皮擦
    
    • 1
    • 2
    • 3

    准备工作完成,正文开始!

    重置

    重置功能其实就是将当前绘画的内容全部清空,而我们用于存储绘画数据的为 PaintedBoardProvider 中的 List _strokes,所以,我们只要将其清空,然后刷下页面即可。

    PaintedBoardProvider 中添加以下方法:

      void clearBoard() {
        _strokes.clear();
        notifyListeners();
      }
    
    • 1
    • 2
    • 3
    • 4

    然后在点击的地方调用:

                          Expanded(
                            child: GestureDetector(
                              onTap: () {
                                print("点击了重置");
                                _paintedBoardProvider.clearBoard();
                              },
                              child: const Center(
                                child: Text("重置"),
                              ),
                            ),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ok,功能完成,就是这么简单,下一个。

    橡皮擦

    关于橡皮擦功能的实现,目前有两种方式:

    • 假清除效果,也就是把画笔颜色改为白色,或者说改为跟画布同一个颜色,然后这样绘画的时候,就会将原有的画笔绘画效果覆盖住,造成一种清除效果的假象。所以这里不建议这样使用,有兴趣的可以自己实现下。
    • 真清除效果,也就是把画笔颜色改为透明,然后用画笔进行绘画的时候,当画笔绘画的内容产生交接时,将其 blendMode 改为 clear,就可以把交接处直接清除。

    下面我们来讲讲真清除效果。

        // 获取绘画数据进行绘画
        for (final stroke in paintedBoardProvider.strokes) {
          final paint = Paint()
            ..strokeWidth = stroke.width
            ..color = stroke.color
            ..strokeCap = StrokeCap.round
            ..style = PaintingStyle.stroke
            ..blendMode = stroke.isClear ? BlendMode.clear : BlendMode.src;  //  <-  新增
          canvas.drawPath(stroke.path, paint);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    看看效果:

    在这里插入图片描述

    em…怎么变成黑色了?虽然说我们是直接在画布上绘画,即使是把背景清除了,也不应该是黑色,因为我也尝试过使用橙色背景的 Container 包裹住 CustomPaint,但是画出的颜色还是黑色。对此了解的大佬麻烦解答下。

    至于解决办法我倒是了解,也就是使用 saveLayer。相当于我们在使用 Photoshop 的时候,新建一个透明蒙层,在新的蒙层上进行绘画,等绘画完成后,我们可以调用 restore 将该蒙层贴回画布中,重新在画布上进行绘制。大致的代码如下:

        canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
        // 获取绘画数据进行绘画
        for (final stroke in paintedBoardProvider.strokes) {
          final paint = Paint()
            ..strokeWidth = stroke.width
            ..color = stroke.color
            ..strokeCap = StrokeCap.round
            ..style = PaintingStyle.stroke
            ..blendMode = stroke.isClear ? BlendMode.clear : BlendMode.src;  //  <-  新增
          canvas.drawPath(stroke.path, paint);
        }
        canvas.restore();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    效果:

    在这里插入图片描述

  • 相关阅读:
    论文阅读[51]通过深度学习快速识别荧光组分
    React TypeScript安装npm第三方包时,些包并不是 TypeScript 编写的
    第一章 硬件选型
    以http协议实现onvif协议并完成对IPC摄像头的监控
    个人头像人工智能生成工具,上线一天就已赚了1万美金
    ComponentAce FlexCompress强大功能
    实战从零开始实现Raft|得物技术
    react antd -Form表单 (单个对象合并成整体对象) 合并对象
    Profinet现场总线耦合器模拟量扩展IO
    Qt Model/View之代理
  • 原文地址:https://blog.csdn.net/m0_46278918/article/details/125191716