learn-tech/专栏/Flutter入门教程/20通过手势在白板上绘制.md
2024-10-16 00:01:16 +08:00

7.8 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        20 通过手势在白板上绘制
                        1. 白板绘制思路分析

对功能需求的分析主要从 界面数据信息、交互与数据维护、界面构建逻辑 三个方面来思考。

数据信息

界面上需要呈现多条线,每条线由若干个点构成,另外可以指定线的颜色和粗细。所以这里可以封装一个 Line 类维护这些数据:

class Line { List points; Color color; double strokeWidth;

Line({ required this.points, this.color = Colors.black, this.strokeWidth = 1, }); }

然后需要 List 线列表来表示若干条线;由于支持颜色和粗细选择,需要给出支持的颜色、粗细选项列表,以及两者的激活索引:

List _lines = []; // 线列表

int _activeColorIndex = 0; // 颜色激活索引 int _activeStorkWidthIndex = 0; // 线宽激活索引

// 支持的颜色 final List supportColors = [ Colors.black, Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.indigo, Colors.purple, ];

// 支持的线粗 final List supportStorkWidths = [1,2, 4, 6, 8, 10];

交互与数据维护

线列表数据的维护和用户的拖拽事件息息相关:

用户开始拖拽开始时,需要创建 Line 对象,加入线列表。 用户拖拽过程中,将触点添加到线列表最后一条线中。 用户点击清除时,清空线列表。 用户可以通过交互选择颜色,更新颜色激活索引。 用户可以通过交互选择线宽,更新线宽激活索引。

界面构建逻辑

在该需求中的数据和交互分析完后,就可以考虑界面的构建逻辑了。下面是一个示意简图:

使用 Scaffold + Appbar 组件构建整体结构。 使用 CustomPaint 组件构建主体内容,作为可绘制区域。 通过 GestureDetector 组件监听拖拽手势,维护数据变化。 通过 Stack 组件将画笔颜色和线粗选择器叠放在绘制区上。 画笔颜色和线粗选择器的构建将在下一篇详细介绍。

到这里,主要的数据和交互,以及实现的思路就分析的差不多了,接下来就进入项目代码的编写。

  1. 手势交互与数据维护

如下,新建一个 paper 文件夹,用于盛放白板绘制的相关代码。其中:

model.dart 中盛放相关的数据模型,比如 Line 类。 paper_app_bar.dart 是抽离的头部标题组件。 paper.dart 是白板绘制的主界面。

首先在 _PaperState 中放入之前分析的数据:

想要监听用户的拖拽手势,可以使用 GestureDetector 组件的 pan 系列回调。如下所示,在 CustomPaint 之上嵌套 GestureDetector 组件;通过 onPanStart 可以监听到用户开始拖拽那刻的事件、通过 onPanUpdate 可以监听到用户拖拽中的事件:

body: GestureDetector( onPanStart: _onPanStart, onPanUpdate: _onPanUpdate, child: CustomPaint( //略同... ), )

void _onPanStart(DragStartDetails details) {}

void _onPanUpdate(DragUpdateDetails details) {}

回调中的逻辑处理也比较简单,在开始拖拽时为线列表添加新线;此时新线就是 _lines 的最后一个元素,在拖拽中,为新线添加点即可:

// 拖拽开始,添加新线 void _onPanStart(DragStartDetails details) { _lines.add(Line(points: [details.localPosition],)); }

// 拖拽中,为新线添加点 void _onPanUpdate(DragUpdateDetails details) { _lines.last.points.add(details.localPosition); setState(() {

}); }

  1. 画板的绘制逻辑

在 PaperPainter 中处理绘制逻辑,绘制过程中需要线列表的数据,而数据在 _PaperState 中维护。可以通过构造函数,将数据传入 PaperPainter 中,以供绘制时使用:

class PaperPainter extends CustomPainter { PaperPainter({ required this.lines, }) { _paint = Paint() ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; }

late Paint _paint; final List lines;

@override void paint(Canvas canvas, Size size) { for (int i = 0; i < lines.length; i++) { drawLine(canvas, lines[i]); } }

///根据点集绘制线 void drawLine(Canvas canvas, Line line) { _paint.color = line.color; _paint.strokeWidth = line.strokeWidth; canvas.drawPoints(PointMode.polygon, line.points, _paint); }

@override bool shouldRepaint(CustomPainter oldDelegate) => true; }

绘制逻辑在上面的 paint 方法中,便历 Line 列表,通过 drawPoints 绘制 PointMode.polygon 类型的点集; Line 对象中的颜色和边线数据,可以在绘制前为画笔设置对应属性。最后,在 _PaperState 状态类中,当 PaperPainter 对象创建时,将 _lines 列表作为入参提供给画板即可:

———————————————————— ————————————————————

--->[_PaperState]---- child: CustomPaint( painter: PaperPainter( lines: _lines ),

4.弹出对话框

在点击清除按钮时,清空线列表。一般对于清除的操作,需要给用户一个确认的对话框,从而避免误操作。如下所示:

点击弹框 确认清除

弹出对话框可以使用框架中提供的 showDialog 方法,在 builder 回调函数中创建需要展示的组件。这里封装了 ConformDialog 组件用于展示对话框,将一些文字描述作为参数。这样其他地方想弹出类似的对话框,可以用 ConformDialog 组件,这就是封装的可复用性。

void _showClearDialog() { String msg = "您的当前操作会清空绘制内容,是否确定删除!"; showDialog( context: context, builder: (ctx) => ConformDialog( title: '清空提示', conformText: '确定', msg: msg, onConform: _clear, )); }

// 点击清除按钮,清空线列表 void _clear() { _lines.clear(); setState(() { }); }

对话框背景是不透明灰色,这种展示效果可以使用 Dialog 组件;弹框整体结构也比较简单,是上中下竖直排列,可以使用 Column 组件;标题、消息内容、按钮这里通过三个函数来构建:

小练习: 自己完成 ConformDialog 中三个构建函数的逻辑。

class ConformDialog extends StatelessWidget { final String title; final String msg; final String conformText; final VoidCallback onConform;

ConformDialog({ Key? key, required this.title, required this.msg, required this.onConform, this.conformText = '删除', }) : super(key: key);

@override Widget build(BuildContext context) { return Dialog( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ _buildTitle(), _buildMessage(), _buildButtons(context), ], ), ), ); } //... }

  1. 本章小结

本章主要完成白板绘制的基础功能,包括整体布局结构、监听拖拽事件,维护点集数据、以及绘制线集数据。其中蕴含着数据和界面展现的关系,比如,数据是如何产生的、数据在用户的交互期间有哪些变化、数据是如何呈现到界面上的,大家可以自己多多思考和理解。

到这里,界面上的线条会随着手指的拖动而呈现,完成了最基础的功能,当前代码位置 paper。下一篇将介绍一下如何通过交互来修改画笔颜色和粗细让界面的呈现更加丰富。