前言:
前天,一位不願意透露姓名的朋友找到我,問我怎麼樣才能把文本變得炫酷一些,他想用圖檔嵌入到自己的名字裡去,用來當作朋友圈的背景。我直接回了一句,你PS下不就好了。他回我一句:想要這樣效果的人比較多,全部都PS的話怕不是電腦要幹冒煙...能不能用代碼自動生成下(請你喝奶茶🍹)。作為一個樂于助人的人,看到朋友有困難,而且實作起來也不複雜,那我必須要幫忙啊~
注:本文是一篇整活文,讓大家看的開心最重要~文章隻對核心代碼做分析,完整代碼在這裡
話不多說,直接上圖:
填入文本中的可以是手動上傳的圖檔,也可以是彩色小塊。
功能實作步驟分析:
1.資料的擷取 — 擷取輸入的文本資料、擷取輸入的圖檔資料。
2.将輸入的文本生成為圖檔
3.解析文本圖檔,替換像素為圖檔
簡單三步驟,實作樸素到炫酷的轉換~
1.資料的擷取 — 擷取輸入的文本資料、擷取輸入的圖檔資料。
- 定義需要存放的資料
//用于擷取輸入的文本
TextEditingController textEditingController = TextEditingController();
//存放輸入的圖檔
List<File> imagesPath = [];
- 輸入框
Container(
margin: const EdgeInsets.all(25.0),
child: TextField(
controller: textEditingController,
decoration: const InputDecoration(
hintText: "請輸入文字",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)))),
),
),
- 九宮格圖檔封裝
@override
Widget build(BuildContext context) {
var maxWidth = MediaQuery.of(context).size.width;
//計算不同數量時,圖檔的大小
var _ninePictureW = (maxWidth - _space * 2 - 2 * _itemSpace - lRSpace);
...
return Offstage(
offstage: imgData!.length == -1,
child: SizedBox(
width: _bgWidth,
height: _bgHeight,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 可以直接指定每行(列)顯示多少個Item
crossAxisCount: _crossAxisCount, // 一行的Widget數量
crossAxisSpacing: _itemSpace, // 水準間距
mainAxisSpacing: _itemSpace, // 垂直間距
childAspectRatio: _childAspectRatio, // 子Widget寬高比例
),
// 禁用滾動事件
physics: const NeverScrollableScrollPhysics(),
// GridView内邊距
padding: const EdgeInsets.only(left: _space, right: _space),
itemCount:
imgData!.length < 9 ? imgData!.length + 1 : imgData!.length,
itemBuilder: (context, index) {
if (imgData!.isEmpty) {
return _addPhoto(context);
} else if (index < imgData!.length) {
return _itemCell(context, index);
} else if (index == imgData!.length) {
return _addPhoto(context);
}
return SizedBox();
}),
),
);
}
-
添加圖檔
使用A佬的
,要的就是效率~wechat_assets_picker
Future<void> selectAssets() async {
//擷取圖檔
final List<AssetEntity>? result = await AssetPicker.pickAssets(
context,
);
List<File> images = [];
//循環取出File
if (result != null) {
for (int i = 0; i < result.length; i++) {
AssetEntity asset = result[i];
File? file = await asset.file;
if (file != null) {
images.add(file);
}
}
}
//更新狀态,修改存放File的數組
setState(() {
imagesPath = images;
});
}
2.将輸入的文本生成為圖檔
- 建構輸入的文本布局
RepaintBoundary(
key: repaintKey,
child: Container(
color: Colors.white,
width: MediaQuery.of(context).size.width,
height: 300,
//image是解析圖檔的資料
child: image != null
? PhotoLayout(
n: 1080,
m: 900,
image: image!,
fileImages: widget.images)
:
//将輸入的文本布局
Center(
child: Text(
widget.photoText,
style: const TextStyle(
fontSize: 100, fontWeight: FontWeight.bold),
),
),
)),
- 通過
将生成的布局生成RepaintBoundary
資料Uint8List
/// 擷取截取圖檔的資料,并解碼
Future<img.Image?> getImageData() async {
//生成圖檔資料
BuildContext buildContext = repaintKey.currentContext!;
Uint8List imageBytes;
RenderRepaintBoundary boundary =
buildContext.findRenderObject() as RenderRepaintBoundary;
double dpr = ui.window.devicePixelRatio;
ui.Image image = await boundary.toImage(pixelRatio: dpr);
// image.width
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
imageBytes = byteData!.buffer.asUint8List();
var tempDir = await getTemporaryDirectory();
//生成file檔案格式
var file =
await File('${tempDir.path}/image_${DateTime.now().millisecond}.png')
.create();
//轉成file檔案
file.writeAsBytesSync(imageBytes);
//存放生成的圖檔到本地
// final result = await ImageGallerySaver.saveFile(file.path);
return img.decodeImage(imageBytes);
}
3.解析文本圖檔,替換像素為圖檔
- 判斷文本像素,在對應像素位置生成圖檔
Widget buildPixel(int x, int y) {
int index = x * n + y;
//根據給定的x和y坐标,擷取像素的顔色編碼
Color color = Color(image.getPixel(y, x));
//判斷是不是白色的像素點,如果是,則用SizedBox替代
if (color == Colors.white) {
return const SizedBox.shrink();
}
else {
//如果不是,則代表是文本所在的像素,替換為輸入的圖檔
return Image.file(
fileImages![index % fileImages!.length],
fit: BoxFit.cover,
);
}
}
- 建構最終生成的圖檔
@override
Widget build(BuildContext context) {
List<Widget> children = [];
//按點去渲染圖檔的像素位置,每次加10是因為,圖像的像素點很多,如果每一個點都替換為圖檔,第一是效果不好,第二是渲染的時間很久。
for (int i = 0; i < n; i = i+10) {
List<Widget> columnChildren = [];
for (int x = 0; x < m; x = x+10) {
columnChildren.add(
Expanded(
child: buildPixel(x, i),
),
);
}
children.add(Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: columnChildren,
)));
}
//CrossAxisAlignment.stretch:子控件完全填充交叉軸方向的空間
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
);
}