前言

我们这篇来了解下撤销、重制的功能,其实也就是 undo 和 redo,在这里我们使用命令模式去设计,若对该模式不了解的话,可以考虑看下 「关于命令模式的误区,你知道了吗」。

其实对于命令模式,我最开始的理解为命令模式只是为了方便数据的管理和记录,不应该和具体的事务或状态进行绑定,后面经过跟同事的“友好”沟通后,感觉命令模式更符合数据的管理+具体事务执行,这个这样才算是一个命令的独立执行过程,而并非只是对数据进行管理,后续的操作还得自己额外去执行。

好了,正文开始。

命令模式实践

首先,新建 ICommand 接口,由于 dart 没有 interface,就用 abstract class 进行代替:

abstract class ICommand{// 执行execute();// 撤销undo();
}

使用 Invoker 进行命令的管理:

class Invoker {// 存储命令内容late List<ICommand> _undoCommands = [];late List<ICommand> _redoCommands = [];/// 执行execute(ICommand command) {_undoCommands.add(command);command.execute();}/// 撤销undo() {if (_undoCommands.isNotEmpty) {final last = _undoCommands.last;_redoCommands.add(last);_undoCommands.remove(last);last.undo();}}/// 重制redo() {if (_redoCommands.isNotEmpty) {final last = _redoCommands.last;_undoCommands.add(last);_redoCommands.remove(last);last.execute();}}
}

关于 Invoker 的设计也是很简单,使用两个 List 去存储命令,分别是 undo 命令列表 和 redo 命令列表。然后执行 undo 和 redo 操作时,其实就是对两个命令列表的数据进行增删操作,同时调用该命令的执行或撤销。

至于具体的命令设计上,每一种操作都应该拥有一种具体的命令实现,由于我这里仅存储画笔的操作,所以我只封装一种命令:

class PaintedCommand extends ICommand {late Stroke _stroke;late PaintedBoardProvider _paintedBoardProvider;PaintedCommand(PaintedBoardProvider paintedBoardProvider, Stroke stroke) {_paintedBoardProvider = paintedBoardProvider;_stroke= stroke;}@overrideexecute() {if (!_paintedBoardProvider.strokes.contains(_stroke)) {_paintedBoardProvider.strokes.add(_stroke);}_paintedBoardProvider.refreshPaintedBoard();}@overrideundo() {if (_paintedBoardProvider.strokes.contains(_stroke)) {_paintedBoardProvider.strokes.remove(_stroke);}_paintedBoardProvider.refreshPaintedBoard();}
}

PaintedCommand 需要使用到 PaintedBoardProvider 和 Stroke,PaintedBoardProvider 用于数据与状态管理,Stroke 用于存储当前命令的数据,而 execute 和 undo 其实就是该命令运作时所需的操作,也就是数据管理+状态更新。

在这里用到了 PaintedBoardProvider 的 refreshPaintedBoard() 方法,其实就是 notifyListeners()

  refreshPaintedBoard(){notifyListeners();}

到了这里有可能有人会问,你这画笔操作不是有两种吗?一种笔刷模式,一种橡皮擦模式,为什么只设计一种命令?

这是因为笔刷模式和橡皮擦模式的区分仅在于 Stroke 中 isClear 的值,所以为了便于管理,使用一种命令更合适。

场景运用

下面我们把刚刚设计好的命令模式应用到具体业务中。

首先,我们得新增两个按钮:undo 和 redo,并且新建 Invoker 对象进行操作 :

class _MyHomePageState extends State<MyHomePage> {final PaintedBoardProvider _paintedBoardProvider = PaintedBoardProvider();final Invoker _invoker = Invoker();   // <- 重点在这里
                      Expanded(child: GestureDetector(onTap: () {print("点击了 undo");_invoker.undo();  //  <-  新增},child: const Center(child: Text("undo"),),),),Expanded(child: GestureDetector(onTap: () {print("点击了redo");_invoker.redo();  //  <-  新增},child: const Center(child: Text("redo"),),),),

剩下的就是要确认命令执行时机。

正常来说,一旦画布进行更改了,就是一次命令,但是由于在手绘期间,画布是一直在不断刷新的,若是这样继续记录,那命令数量就过于庞大了,所以这里我该改为一次手势完整流程就是一次命令,这样我们可以在 onPanEnd 进行记录即可。

所以,我们先把 _invoker 传递给 HandPaintedBoard:

                Expanded(child: HandPaintedBoard(_paintedBoardProvider, _invoker)),  // <- 重点在这里
class HandPaintedBoard extends StatefulWidget {const HandPaintedBoard(this._paintedBoardProvider, this._invoker, {  // <- 更改Key? key,}) : super(key: key);final PaintedBoardProvider _paintedBoardProvider;final Invoker _invoker;  // <- 更改

然后在 onPanEnd 进行命令的执行:

      onPanEnd: (details) {print("onPanDown:移动结束");widget._invoker.execute(PaintedCommand(   // <- 命令执行_paintedBoardProvider, _paintedBoardProvider.strokes.last));},

由此,整个效果就完成了。

手绘板的制作——命令模式与撤销、重制(3)相关推荐

  1. 手绘板的制作——画布保存(6)

    「手绘板的制作--手绘(1)」 「手绘板的制作--重置与橡皮擦(2)」 「手绘板的制作--命令模式与撤销.重制(3)」 「手绘板的制作--画布缩放(4)」 「手绘板的制作--画布移动(5)」 前言 经 ...

  2. 手绘板的制作——画布移动(5)

    前言 在上文「手绘板的制作--画布缩放(4)」中,我们学会了画布的缩放,这节我们学习下画布的移动,毕竟放大的画布不能移动的话,那放大还有什么意义.=_= 手势检测 既然要移动,那当然需要检测手势,由于 ...

  3. 手绘板的制作——手绘(1)

    前言 通过上一篇文章「如何优雅地画一张图」我们已经知道如何在画布里面绘画一张图了,这次我准备开一个系列讲解下手绘板的制作,可能包含: 手绘 橡皮擦 撤销 重制 重置 图片导出 命令模式 等功能.具体等 ...

  4. 手绘板的制作——画布缩放(4)

    前言 在这一篇中,我们讲解下画布的缩放,也就是做一个根据手势缩放进行画布缩放的功能. 我们先来梳理下逻辑: 监听手势,当为一根手指的时候,就延续之前的操作,执行手绘操作,当操作为两根手指的时候,则执行 ...

  5. Android View与SurfaceView的手绘板制作

    最近学习了如何使用View与SurfaceView制作简单的手绘板,在此做个小结. 自定义VIew实现手绘板: 首先是使用View来实现手绘板: package com.app.superxlcr.m ...

  6. java画图颜色_手绘板,多种颜色选择。我抄的《疯狂java讲义》的,包我乱导的,但代码能用。...

    [java]代码库import javax.swing.*; import java.awt.image.*; import java.awt.datatransfer.*; import javax ...

  7. Photoshop---Wacom手绘板绘画画变成了拖动,根本不能画画

    前述 以下步骤都是基于windows10上的结果,而且的确是解决了我的问题,windows7没有试过,估计差不多,分享出来希望能帮到众位仙家. 背景&问题 近段时间photoshop手绘的时候 ...

  8. 手绘线条一直画不直_手绘板线条画不直怎么办?板绘画线诀窍分享

    在数位板画画和在纸上画画是有一定区别的,线条很难控制,手绘板线条画不直,那么如何在数位板上画出流畅线条?越是习惯手绘的朋友,那你可能就需要画更多的时间来适应数位板的画图方式,今天微课菌给大家分享一组板 ...

  9. 什么是数位板? 数位板,又名绘图板、绘画板、手绘板等等,是计算机输入设备的一种,通常是由一块板子和一支压感笔组成,它和手写板等作为非常规的输入产品相类似,都针对一定的使用群体。 与手写板所不同的是

    什么是数位板? 数位板,又名绘图板.绘画板.手绘板等等,是计算机输入设备的一种,通常是由一块板子和一支压感笔组成,它和手写板等作为非常规的输入产品相类似,都针对一定的使用群体. 与手写板所不同的是,数 ...

最新文章

  1. java 简单万年历_JAVA实现的简单万年历代码
  2. norm--求矩阵和向量的范数
  3. HDU 1114(没有变形的完全背包)
  4. js控制a标签点击事件 触发下载
  5. 全国计算机等级考试题库二级C操作题100套(第82套)
  6. 数据类型的转换小结 c# 1614092651
  7. Java开发找工作最懵圈的问题:到底啥是分布式系统开发经验?
  8. ARM处理器与架构对应关系
  9. 基于卷积神经网络和迁移学习实现场景图片分类任务
  10. VS 2013安装教程
  11. 图像分割(多分类)将mask变为one hot label
  12. Html源码在线翻译,HTML – 谷歌翻译网站
  13. tomcat设置一级域名、二级域名访问指定项目
  14. 矩阵求逆的一万种方法
  15. 红色警戒2+尤里的复仇,带完整音乐+影片,免安装版本,完美兼容WIN10
  16. 郑州 - 天气总是灰蒙蒙的
  17. Yocto中WIC控制以及WKS文件
  18. 【秋钓皮皮】 奔跑吧,皮皮!(有图了)
  19. 二度云抢先成为首批工信部(.vip/.xyz/.club)域名注册管理机构
  20. 北航计算机就业2018,2020年考研:北京航空航天大学2018年的毕业生就业情况怎样?北航就业前景分析...

热门文章

  1. keil玩儿51单片机时遇见的错误与警告
  2. python多线程内存溢出_ThreadPoolExecutor使用后内存溢出(一)
  3. 分享7个素材网帮你轻松解决,新手做自媒体没播放?收益少?
  4. chrome android 分屏,谷歌调整安卓系统:分屏多任务同时支持两款以上APP!
  5. Ubuntu18.04使用静态ip
  6. 计算机图形(Cha.3 光栅化)
  7. 我是一名程序员,帮我写一篇年终述职报告
  8. 2020山东春季高考计算机专业,2020山东春季高考科目时间及总分
  9. OSChina 周四乱弹 —— 老公你回来啦?
  10. vue中使用excel单个/批量导出表格数据