【端午】会说话的粽子你见过吗?

语言: CN / TW / HK

我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

前言

大家好,端午将至,首先提前祝掘金的小伙伴端午安康,端午作为中华民族的非常重要的传统节日,粽子那是必不可少的,但是你真的知道粽子的历史吗? 今天跟随本篇文章用Flutter path画一个会科普节日的的粽子吧~

绘制

1、基本轮廓

首先我们需要将粽子的基本轮廓绘制出来,通过图片可以看到粽子的轮廓是一个圆圆的三角形状,
本篇文章所有的图形都是用纯Path路径制作,这里我们可以将粽子的轮廓分为三个二级贝塞尔曲线来进行绘制,头、左右粽叶轮廓。 核心代码: ```dart

canvas.translate(size.width / 2, size.height / 2); canvas.translate(-50, -50); Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2 ..color = Colors.black ..isAntiAlias = true;

Path path = Path(); path.relativeQuadraticBezierTo(50, -80, 100, 0); path.relativeQuadraticBezierTo(90, 130, -50, 130); path.relativeQuadraticBezierTo(-140, 0, -50, -130); path.close(); canvas.drawPath(path, paint); ``` 效果图:
image.png

### 2、粽叶 有了基本轮廓,接下来我们需要绘制粽叶,可以看到粽叶的形状是一个不规则的形状,这里可以使用Path路径联合,两个路径生成一个新的路径,这样我们就可以得到左边粽叶的区域, 核心代码:
```dart Path path2 = Path(); path2.relativeQuadraticBezierTo(60, 100, 190, 130); path2.relativeLineTo(0, 40); path2.relativeLineTo(-260, 0); path2.relativeLineTo(0, -200); path2.close();

canvas.drawPath( path2, paint ..color = Color(0xFF2A9200)); ``` 效果图:
image.png

然后使用路径联合取这两个区域的交集,即可得到粽叶左边的区域。
核心代码: ```dart Path pathStart = Path.combine(PathOperation.intersect, path, path2); pathStart.close();

canvas.drawPath( pathStart, paint ..color = Color(0xFF2A9200) ..style = PaintingStyle.fill); ``` 效果图:
image.png

有了区域以后,我们需要再来点纹路,看起来更像粽叶,这里继续使用路径联合,我们只需要将上方我们合成的新路径向左下方偏移多次即可。 核心代码: dart _canvasStartLines(Canvas canvas, Path pathStart, Paint paint) { for (int i = 1; i < 10; i++) { Path path = Path(); path.moveTo(-8 * i.toDouble(), 8 * i.toDouble()); path.relativeQuadraticBezierTo(60, 100, 190, 130); path.relativeLineTo(0, 60); path.relativeLineTo(-300, 0); path.relativeLineTo(0, -200); path.close(); canvas.drawPath( Path.combine(PathOperation.intersect, pathStart, path), paint ..color = Colors.black ..style = PaintingStyle.stroke); } } 效果图:
image.png

接下来右边粽叶同理:核心代码
这里就不贴了,想看的小伙伴参考demo,地址在最下方,已上传github。

效果图:
image.png

粽叶基本就完成啦。

### 嘴巴 嘴巴我们就用一个三阶贝塞尔曲线闭合绘制一个开心的表情。
核心代码:
dart Path path4 = Path(); path4.moveTo(40, 20); path4.relativeCubicTo(2, 18, 18, 18, 20, 0); path4.close(); canvas.drawPath(path4, paint..color = Colors.black87); 效果图:
image.png ### 眼睛 眼睛我们也用两个三阶贝塞尔曲线,开心的样子。 /// 眼睛 Path path5 = Path(); path5.moveTo(20, 5); path5.relativeCubicTo(5, -10, 15, -10, 20, 0); canvas.drawPath( path5, paint ..color = Colors.black87 ..style = PaintingStyle.stroke); canvas.save(); canvas.translate(40, 0); canvas.drawPath( path5, paint ..color = Colors.black87 ..style = PaintingStyle.stroke); canvas.restore(); 效果图:
image.png

腮红

接下来给面部设置下肤色,然后添加一点点细节。这里我们给path路径添加一个椭圆点缀那么一下。 核心代码: dart /// 晒红 Path path9 = Path(); path9.addArc(Rect.fromCenter(center: Offset(30,30), width: 4, height: 6),0,pi*2); canvas.drawPath(path9, paint..color = Color(0xFFFFA2AE)..style = PaintingStyle.fill); canvas.save(); canvas.translate(6, 0); canvas.drawPath(path9, paint..color = Color(0xFFFFA2AE)..style = PaintingStyle.fill); canvas.restore(); canvas.save(); canvas.translate(34, 0); canvas.drawPath(path9, paint..color = Color(0xFFFFA2AE)..style = PaintingStyle.fill); canvas.restore(); canvas.save(); canvas.translate(40, 0); canvas.drawPath(path9, paint..color = Color(0xFFFFA2AE)..style = PaintingStyle.fill); canvas.restore(); 效果图:
image.png ### 手&脚 看着光溜溜的没有手脚怎么行,接下来我们继续使用path路径给粽子添加手脚,这里有一个知识点就是我们需要找到手脚的位置坐标在哪,就需要使用到 path的路径测量,根据路径上的点找到我们合适的手脚位置。
通过path.computeMetrics()我们可以得到一个路径的迭代对象PathMetric() ,这个迭代对象里面包含这个路径所有图形的很多信息,我们都可以从这个对象得到,这里我们从粽叶路径中得到我们的手脚的坐标点。绘制手脚, 这里只贴了左手的代码,其他同理。
核心代码: dart ///粽叶路径 ///左边 var pms = pathStart.computeMetrics(); var first = pms.first; var offsetStart = first.getTangentForOffset(first.length * 0.55)!; /// 手 Path path7 = Path(); path7.moveTo(offsetStart.position.dx, offsetStart.position.dy); path7.relativeLineTo(-30, 20); path7.relativeLineTo(-5, -30); /// 左手 canvas.drawPath( path7, paint ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 3); 效果图: image.png

### 头巾 粽子有了,接下来给粽子来个标记,我是甜粽子还是咸粽子,毕竟有人爱吃咸粽子,有人爱吃甜粽子, 这里我们用路径绘制一个头绳,然后在头绳中间对标记粽子的咸甜。
这里知识点需要掌握绘制文字,核心代码: ```dart Path path6 = Path(); path6.moveTo(0, -50); path6.quadraticBezierTo(50, 10, 100, -50); canvas.drawPath( Path.combine(PathOperation.intersect, path, path6), paint ..color = Colors.pink ..style = PaintingStyle.stroke);

var textPainter = TextPainter( text: TextSpan( text: "甜", style: TextStyle(fontSize: 16, color: Colors.white)), textDirection: TextDirection.ltr); textPainter.layout(); var size2 = textPainter.size;

canvas.drawCircle( Offset(50, -20), size2.width, paint ..color = Colors.pink ..style = PaintingStyle.fill); textPainter.paint( canvas, Offset(-size2.width / 2, -size2.height / 2).translate(50, -20)); ``` 效果图: image.png

### 咸甜是一家 有甜粽子那也得有咸粽子不是,我们只需将上面粽子的代码复制,改下文字就可以制作一个咸粽子啦。 效果图: image.png 背景有点空,再加个背景图:
image.png OK,粽子的绘制工作到这里已经结束了,接下来我们让粽子说话可以给小朋友科普端午节的来历吧。

## 发声 粽子制作完成,接下来我们需要让粽子会说话,这里我使用的讯飞的语音合成WebAPI流式传输数据,将文本转化为音频文件实时播放,只需要调用接口即可将文本转化为音频文件播放,这里需要web_socket_channel插件来和讯飞进行webSocket连接,这样做的好处是不需要集成任何sdk,只需要通过APi接口就可以实时转换, image.png

web_socket_channel的简单使用:
创建连接:
image.png
发送消息方法:_channel?.sink.add(data);
在listen监听接收信息。
具体实现步骤已经整理文章分享: Flutter实现讯飞在线语音合成(WebSocket流式版)。

### 动画控制嘴巴开合 这里我们就让甜粽子为我们讲解,动画绘制之间的配合使用之前的文章介绍过多次,这里就不再过多介绍,直接看效果吧。
- 嘴巴张合运动曲线:
d8de77b13cc41ee8e0e97d6bf50e35d5.gif
身体也加一个默认的运动曲线。
效果图:

1653970078130.gif 科普文字
image.png 加上科普文章,加上声音,此处已经有声音科普了哦,想体验的小伙伴可以下载源码自己体验一下,传送门 demo地址

1653970114575.gif 这里我们使用童声豆豆的声音, image.png 设置到这里即可。 示例代码demo地址 image.png

用到的技术点

绘制: path路径贝塞尔曲线路径联合路径测量路径添加图形绘制文字绘制图片域
动画: 多动画与绘制联合使用。
通信: web_socket_channel 的使用。
文件操作: 插件 path_provider 写入文件。
播放音频操作: 插件 audioplayers 播放音频。

总个结

此灵感来源于科普中国传统节日的视频,然后就有了这篇文章,因为之前做智能家具有在客户端集成过讯飞语音,所以我又去了讯飞官方文档看有没有Flutter插件,但是很显然没有,但是看到有通过WebSocket流形式来进行传输的时候,我觉得嗯,就是你了,但是找demo的时候,又没有dart语言的版本,跟着文档一步一步也最终实现了需求,WebSocket方式非常适合跨平台应用的使用,无需集成任何SDK,只通过流的传输即可完成转换。如果使用SDK集成在某一端确实很方便,但是对于跨平台应用就显得比较笨重。之后有时间在研究下讯飞的语音识别,应该大同小异。OK,那本篇文章就到这里了。最后提前祝掘金的小伙伴端午安康,生活香满园。