# 【-Flutter绘制集录-】第一画: 随机对称点头像

#### 零、前言

##### 1. 关于FlutterUnit 绘制集录

FlutterUnit绘制集录已拉开序幕,此集录会收录一些有意思的绘制作品，或一些典型的绘制样例来让大家接触Flutter更广大的可能性。(`下面的黑框也是绘制出来的哦`)

The ChaosRandom PortraitTriangular MeshHypnotic Squares

#### 2.关于本文画作 相关源码见这里

``````[1]. 可指定每行(列)的格子个数,且为奇数
[2]. 图形成左右对称
[3]. 半侧的图像点随机出现随机个

5*55*59*9
9*911*1111*11

##### 3.这有什么用？
``````[1]. 练习绘制能力
[2]. 练习操纵数据的能力
[3]. 将widget保存为图片,你能获得默认头像
[4]. 最重要的是,挺好玩的~

#### 一、画布的栅格与坐标

##### 1. 基本思路

``````class Position {
final int x;
final int y;

Position(this.x, this.y);

@override
String toString() {
return 'Position{x: \$x, y: \$y}';
}
}

##### 2. 从一个点开始

`Rect.fromLTWH`可以根据左上角坐标和矩形宽高绘制矩形

Position(1, 1) | Position(4, 3)| Position(3, 2) | ---|---|---|--- | ||

``````class PortraitPainter extends CustomPainter {
Paint _paint;//画笔
final int blockCount = 5; // 块数
final position = Position(1, 1); //点位

PortraitPainter():
_paint = Paint()..color = Colors.blue;

@override
void paint(Canvas canvas, Size size) {
// 裁剪当前区域
canvas.clipRect(
Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));

var perW = size.width / blockCount;
var perH = size.height / blockCount;
_drawBlock(perW, perH, canvas, position);
}

// 绘制块
void _drawBlock(double perW, double perH, Canvas canvas, Position position) {
canvas.drawRect(
Rect.fromLTWH(position.x * perW, position.y * perH, perW, perH), _paint);
}

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

##### 3. 绘制多点

``````final List<Position> positions = [
Position(1, 0),
Position(2, 1),
Position(0, 1),
Position(0, 2),
Position(1, 3),
Position(2, 4),
Position(3, 0),
Position(2, 1),
Position(4, 1),
Position(4, 2),
Position(3, 3),
];

@override
void paint(Canvas canvas, Size size) {
//英雄所见...
// 遍历坐标集, 绘制块
positions.forEach((element) {
_drawBlock(perW, perH, canvas, element);
});
}

#### 二、随机数和数据操作

##### 1. 画板类:PortraitPainter
``````class PortraitPainter extends CustomPainter {
Paint _paint;

final int blockCount;
final Color color;
final List<Position> positions;

PortraitPainter(this.positions, {this.blockCount = 9,this.color=Colors.blue})
: _paint = Paint()..color = color;

@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(
Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));

var perW = size.width / blockCount;
var perH = size.height / blockCount;

positions.forEach((element) {
_drawBlock(perW, perH, canvas, element);
});
}

void _drawBlock(double dW, double dH, Canvas canvas, Position position) {
canvas.drawRect(
Rect.fromLTWH(position.x * dW, position.y * dH, dW, dH), _paint);
}

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

##### 2.组件类:RandomPortrait

``````class RandomPortrait extends StatefulWidget {
@override
_RandomPortraitState createState() => _RandomPortraitState();
}

class _RandomPortraitState extends State<RandomPortrait> {
List<Position> positions = [];
Random random = Random();
final int blockCount = 9;

@override
Widget build(BuildContext context) {
_initPosition();
return GestureDetector(
onTap: () {
setState(() {});
},
child: CustomPaint(
painter: PortraitPainter(positions, blockCount: blockCount)));
}

void _initPosition() {
// TODO 生成坐标点集
}
}

##### 3.生成点集

``````如果a点和b点关于x=c对称。

123
``````  void _initPosition() {
positions.clear(); // 先清空点集

// 左半边的数量 (随机)
int randomCount = 2 + random.nextInt(blockCount * blockCount ~/ 2 - 2);
// 对称轴
var axis = blockCount ~/ 2 ;
//添加左侧随机点
for (int i = 0; i < randomCount; i++) {
int randomX = random.nextInt(axis+ 1);
int randomY = random.nextInt(blockCount);
var position = Position(randomX, randomY);
}
//添加对称点
for (int i = 0; i < positions.length; i++) {
if (positions[i].x < blockCount ~/ 2) {
positions
.add(Position(2 * axis - positions[i].x, positions[i].y));
}
}
}

##### 4. 小优化

[1]. 可以在绘制时留些边距，这样好看些
[2]. 当格数为9*9时，由于除不尽，可能导致相连块的小间隙(下图2),可以通过边长取整来解决

``````class PortraitPainter extends CustomPainter {
Paint _paint;

final int blockCount;
final Color color;
final List<Position> positions;

final pd = 20.0;

PortraitPainter(this.positions,
{this.blockCount = 9, this.color = Colors.blue})
: _paint = Paint()..color = color;

@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(
Rect.fromPoints(Offset.zero, Offset(size.width, size.height)));

var perW = (size.width - pd * 2) / (blockCount);
var perH = (size.height - pd * 2) / (blockCount);

canvas.translate(pd, pd);
positions.forEach((element) {
_drawBlock(perW, perH, canvas, element);
});
}

void _drawBlock(double dW, double dH, Canvas canvas, Position position) {
canvas.drawRect(
Rect.fromLTWH(
position.x * dW.floor()*1.0,
position.y * dH.floor()*1.0,
dW.floor()*1.0,
dH.floor()*1.0), _paint);
}

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

#### 三、canvas绘制保存为图片

##### 1.Widget2Image组件

``````class Widget2Image extends StatefulWidget {
final Widget child;
final ui.ImageByteFormat format;

Widget2Image(
{@required this.child,
this.format = ui.ImageByteFormat.rawRgba});

@override
Widget2ImageState createState() => Widget2ImageState();

static Widget2ImageState of(BuildContext context) {
final Widget2ImageState result = context.findAncestorStateOfType<Widget2ImageState>();
if (result != null)
return result;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'Widget2Image.of() called with a context that does not contain a Widget2Image.'
),
]);
}
}

class Widget2ImageState extends State<Widget2Image> {
final GlobalKey _globalKey = GlobalKey();

@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: _globalKey,
child: widget.child,
);
}

return _widget2Image(_globalKey);
}

Future<Uint8List> _widget2Image(GlobalKey key) async {
RenderRepaintBoundary boundary = key.currentContext.findRenderObject();
//获得 ui.image
ui.Image img = await boundary.toImage();
//获取图片字节
var byteData = await img.toByteData(format: widget.format);
Uint8List bits = byteData.buffer.asUint8List();
return bits;
}
}

##### 2. 使用 `Widget2Image`
``````  @override
Widget build(BuildContext context) {
_initPosition();
return Widget2Image( // 使用
format: ImageByteFormat.png,
child: Builder( // 使用Builder,让上下文下沉一级
builder: (ctx) => GestureDetector(
onTap: () {
setState(() {});
},
onLongPress: () async { // 长按时执行获取图片方法

// 获取到图片字节数据 ---- 之后可随意操作
final dir = await getTemporaryDirectory();
final dest = path.join(dir.path, "widget.png");
await File(dest).writeAsBytes(bytes);
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("图片已保存到:\$dest")));
},
child: CustomPaint(
painter: PortraitPainter(positions, blockCount: blockCount)),
),
));
}

`@张风捷特烈 2020.10.11 未允禁转`
`~ END ~`