

Flutter 绘制集录 | 随机对称图案
source link: https://mp.weixin.qq.com/s?__biz=MzI0NjU3MDA4NQ%3D%3D&%3Bmid=2247484609&%3Bidx=1&%3Bsn=8c372ed663362a13edf3b2d264240a0d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

关于本文画作
看到GitHub头像,有感而发。默认头像是一个5*5的格子,随机填充色块形成的图形
1[1]. 可指定每行(列)的格子个数,且为奇数
2[2]. 图形成左右对称
3[3]. 半侧的图像点随机出现随机个

效果展示
5*5 5*5 9*9





一、画布的栅格与坐标
1. 基本思路
如下: 将我们的白板想象成一个栅格( 当然你可以在纸上打打草稿,没必要画出来
),这样就很容易看出关系。这时白板就变成了一个 平面坐标系
,我们可以用一个 二维坐标点
描述一个位置。再绘制出来这个矩形。

现在创建Position类用于描述坐标位置。
1class Position {
2 final int x;
3 final int y;
4
5 Position(this.x, this.y);
6
7 @override
8 String toString() {
9 return 'Position{x: $x, y: $y}';
10 }
11}
2. 从一个点开始
将一个 Position
对象和 栅格中的一个矩形区域
对应起来
Rect.fromLTWH
可以根据左上角坐标和矩形宽高绘制矩形



1class PortraitPainter extends CustomPainter {
2 Paint _paint;//画笔
3 final int blockCount = 5; // 块数
4 final position = Position(1, 1); //点位
5
6 PortraitPainter():
7 _paint = Paint()..color = Colors.blue;
8
9 @override
10 void paint(Canvas canvas, Size size) {
11 // 裁剪当前区域
12 canvas.clipRect(
13 Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));
14
15 var perW = size.width / blockCount;
16 var perH = size.height / blockCount;
17 _drawBlock(perW, perH, canvas, position);
18 }
19
20 // 绘制块
21 void _drawBlock(double perW, double perH, Canvas canvas, Position position) {
22 canvas.drawRect(
23 Rect.fromLTWH(position.x * perW, position.y * perH, perW, perH), _paint);
24 }
25
26 @override
27 bool shouldRepaint(PortraitPainter oldDelegate) => true;
28}
3. 绘制多点
当你能绘制一个点时,这个问题就已经从 图像问题
转化为 坐标问题
使用坐标集 List<Position>
,通过 遍历坐标集, 绘制矩形块
即可


1final List<Position> positions = [
2 Position(1, 0),
3 Position(2, 1),
4 Position(0, 1),
5 Position(0, 2),
6 Position(1, 3),
7 Position(2, 4),
8 Position(3, 0),
9 Position(2, 1),
10 Position(4, 1),
11 Position(4, 2),
12 Position(3, 3),
13];
14
15@override
16void paint(Canvas canvas, Size size) {
17 //英雄所见...
18 // 遍历坐标集, 绘制块
19 positions.forEach((element) {
20 _drawBlock(perW, perH, canvas, element);
21 });
22}
二、随机数和数据操作
上面已经完成了数据与图形的对应关系,达到了 数即形,形即数的数形合一
境界。
一般在画板类中接收数据,画板中仅进行绘制的相关操作,可以提取出需要DIY的变量。
1. 画板类:PortraitPainter
1class PortraitPainter extends CustomPainter {
2 Paint _paint;
3
4 final int blockCount;
5 final Color color;
6 final List<Position> positions;
7
8 PortraitPainter(this.positions, {this.blockCount = 9,this.color=Colors.blue})
9 : _paint = Paint()..color = color;
10
11 @override
12 void paint(Canvas canvas, Size size) {
13 canvas.clipRect(
14 Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));
15
16 var perW = size.width / blockCount;
17 var perH = size.height / blockCount;
18
19 positions.forEach((element) {
20 _drawBlock(perW, perH, canvas, element);
21 });
22 }
23
24 void _drawBlock(double dW, double dH, Canvas canvas, Position position) {
25 canvas.drawRect(
26 Rect.fromLTWH(position.x * dW, position.y * dH, dW, dH), _paint);
27 }
28
29 @override
30 bool shouldRepaint(PortraitPainter oldDelegate) => true;
31}
2.组件类:RandomPortrait
通过 CustomPaint
使用画板,这里为了方便演示,点击时会刷新重建图形
现在只需要按照需求完成坐标点的生成即可。
1class RandomPortrait extends StatefulWidget {
2 @override
3 _RandomPortraitState createState() => _RandomPortraitState();
4}
5
6class _RandomPortraitState extends State<RandomPortrait> {
7 List<Position> positions = [];
8 Random random = Random();
9 final int blockCount = 9;
10
11 @override
12 Widget build(BuildContext context) {
13 _initPosition();
14 return GestureDetector(
15 onTap: () {
16 setState(() {});
17 },
18 child: CustomPaint(
19 painter: PortraitPainter(positions, blockCount: blockCount)));
20 }
21
22 void _initPosition() {
23 // TODO 生成坐标点集
24 }
25}
3.生成点集
思路是先 生成左半边的点
,然后遍历点,左侧非中间的点时,添加对称点。关于对称处理:
1如果a点和b点关于x=c对称。
2则 (a.x + b.x)/2 = c
3即 b.x = 2*c - a.x



1 void _initPosition() {
2 positions.clear(); // 先清空点集
3
4 // 左半边的数量 (随机)
5 int randomCount = 2 + random.nextInt(blockCount * blockCount ~/ 2 - 2);
6 // 对称轴
7 var axis = blockCount ~/ 2 ;
8 //添加左侧随机点
9 for (int i = 0; i < randomCount; i++) {
10 int randomX = random.nextInt(axis+ 1);
11 int randomY = random.nextInt(blockCount);
12 var position = Position(randomX, randomY);
13 positions.add(position);
14 }
15 //添加对称点
16 for (int i = 0; i < positions.length; i++) {
17 if (positions[i].x < blockCount ~/ 2) {
18 positions
19 .add(Position(2 * axis - positions[i].x, positions[i].y));
20 }
21 }
22 }
这样基本上就完成了,后面可以做些优化
4. 小优化
[1]. 可以在绘制时留些边距,这样好看些
[2]. 当格数为9*9时,由于除不尽,可能导致相连块的小间隙(下图2),可以通过边长取整来解决
留边距 小间隙 小间隙优化


1class PortraitPainter extends CustomPainter {
2 Paint _paint;
3
4 final int blockCount;
5 final Color color;
6 final List<Position> positions;
7
8 final pd = 20.0;
9
10 PortraitPainter(this.positions,
11 {this.blockCount = 9, this.color = Colors.blue})
12 : _paint = Paint()..color = color;
13
14 @override
15 void paint(Canvas canvas, Size size) {
16 canvas.clipRect(
17 Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));
18
19 var perW = (size.width - pd * 2) / (blockCount);
20 var perH = (size.height - pd * 2) / (blockCount);
21
22 canvas.translate(pd, pd);
23 positions.forEach((element) {
24 _drawBlock(perW, perH, canvas, element);
25 });
26 }
27
28 void _drawBlock(double dW, double dH, Canvas canvas, Position position) {
29 canvas.drawRect(
30 Rect.fromLTWH(
31 position.x * dW.floor()*1.0,
32 position.y * dH.floor()*1.0,
33 dW.floor()*1.0,
34 dH.floor()*1.0), _paint);
35 }
36
37 @override
38 bool shouldRepaint(PortraitPainter oldDelegate) => true;
39}
三、canvas绘制保存为图片
可以通过很多方法来读取一个Widget对应的图片数据,这里我使用 RepaintBoundary
,并简单封装了一下。获取图片数据后,可以根据需求保存到本地成为图片,也可以发送到服务器中,作为用户头像。反正字节流在手,万事无忧。

1.Widget2Image组件
简单封装一下,简化Widget2Image的操作流程。
1class Widget2Image extends StatefulWidget {
2 final Widget child;
3 final ui.ImageByteFormat format;
4
5 Widget2Image(
6 {@required this.child,
7 this.format = ui.ImageByteFormat.rawRgba});
8
9 @override
10 Widget2ImageState createState() => Widget2ImageState();
11
12
13 static Widget2ImageState of(BuildContext context) {
14 final Widget2ImageState result = context.findAncestorStateOfType<Widget2ImageState>();
15 if (result != null)
16 return result;
17 throw FlutterError.fromParts(<DiagnosticsNode>[
18 ErrorSummary(
19 'Widget2Image.of() called with a context that does not contain a Widget2Image.'
20 ),
21 ]);
22 }
23}
24
25class Widget2ImageState extends State<Widget2Image> {
26 final GlobalKey _globalKey = GlobalKey();
27
28 @override
29 Widget build(BuildContext context) {
30 return RepaintBoundary(
31 key: _globalKey,
32 child: widget.child,
33 );
34 }
35
36 Future<Uint8List> loadImage() {
37 return _widget2Image(_globalKey);
38 }
39
40 Future<Uint8List> _widget2Image(GlobalKey key) async {
41 RenderRepaintBoundary boundary = key.currentContext.findRenderObject();
42 //获得 ui.image
43 ui.Image img = await boundary.toImage();
44 //获取图片字节
45 var byteData = await img.toByteData(format: widget.format);
46 Uint8List bits = byteData.buffer.asUint8List();
47 return bits;
48 }
49}
2. 使用 Widget2Image
1 @override
2 Widget build(BuildContext context) {
3 _initPosition();
4 return Widget2Image( // 使用
5 format: ImageByteFormat.png,
6 child: Builder( // 使用Builder,让上下文下沉一级
7 builder: (ctx) => GestureDetector(
8 onTap: () {
9 setState(() {});
10 },
11 onLongPress: () async { // 长按时执行获取图片方法
12 var bytes = await Widget2Image.of(ctx).loadImage();
13
14 // 获取到图片字节数据 ---- 之后可随意操作
15 final dir = await getTemporaryDirectory();
16 final dest = path.join(dir.path, "widget.png");
17 await File(dest).writeAsBytes(bytes);
18 Scaffold.of(context)
19 .showSnackBar(SnackBar(content: Text("图片已保存到:$dest")));
20 },
21 child: CustomPaint(
22 painter: PortraitPainter(positions, blockCount: blockCount)),
23 ),
24 ));
25 }
本文到这来就接近尾声了,应该是蛮有意思的。其实根据坐标系,可以做出很多有意思的东西。比如并非一定是画矩形,也可以画圆、三角形、甚至是图片。
如果把栅格分的更细些,这就很像一个 像素世界
。基于此,做个俄罗斯方块或者贪吃蛇什么的应该也可以。
最想说的一点是: 驱动视图显示的是背后的数据, 脑洞会让数据拥有无限可能
。
Recommend
-
68
Hannah 时尚图案 长款雨衣 69元包邮(Z秒杀),来自什么值得买甄选出的亚马逊中国优惠产品,汇聚数十万什么值得买网友对该网购产品的点评。
-
27
新浪科技讯北京时间6月20日消息,在一项新研究中,科学家创造出了一种极其复杂,以至于不可能复制或伪造的图案,这一成果将有效地打击造假者。日本筑波大学的研究人员表示,这些图案的关键在于一个两步验证系统,将微图案与回音壁的基本原理整合在一起。在
-
13
一、组件使用 1、组件地址与功能简介 【pub地址 】 【github地址】 1dependencies:2 flutter_star: $lastVersion 目标: 使用canvas手工...
-
21
一、本作介绍和准备 1. 效果图 如下图,通过 Flutter 的 Canvas 绘制如下的 静态 表面。 本文知识点
-
14
FlutterUnit 下载体验: 复制链接,在浏览器下载~ 当前Flutter 版本 Flutter 2.2.0 • channel stable • https://github.com/flutter/flutter.git Framework • revision b22742018b (7 days ago) • 20...
-
2
有的时候手机上也需要强密码生成器,这样就不用再 PC 上生成后复制过去了,用 Flutter 自己来打造一个吧。老铁记得 转发 ,猫哥会呈现更多 Flutter 好文~~~~微信群 ducafecatb 站 ht...
-
9
按下右侧的“点击预览”按钮可以在当前页面预览,点击链接可以全屏预览。https://codepen.io/comehope/pen/QWvVBJq源代码下载每日前端实战系列的全部源代码请从 github 下载:...
-
9
蜡染图案+皮影偶戏!2022年G20峰会会徽发布! - 标志情报局2022年
-
7
LaTeX 绘制美丽的图案-Tikz 使用 ...
-
4
花束/花卉图案插画设计,绘制自己喜欢的小插画! 更新时间:2023-03-07 20:00:19 本文将介绍一个花束/花卉插画设计的临摹教程,...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK