天天看點

Flutter — 僅用三個步驟就能幫你把文本變得炫酷!

前言:

前天,一位不願意透露姓名的朋友找到我,問我怎麼樣才能把文本變得炫酷一些,他想用圖檔嵌入到自己的名字裡去,用來當作朋友圈的背景。我直接回了一句,你PS下不就好了。他回我一句:想要這樣效果的人比較多,全部都PS的話怕不是電腦要幹冒煙...能不能用代碼自動生成下(請你喝奶茶🍹)。作為一個樂于助人的人,看到朋友有困難,而且實作起來也不複雜,那我必須要幫忙啊~

注:本文是一篇整活文,讓大家看的開心最重要~文章隻對核心代碼做分析,完整代碼在​​這裡​​

話不多說,直接上圖:

填入文本中的可以是手動上傳的圖檔,也可以是彩色小塊。

Flutter — 僅用三個步驟就能幫你把文本變得炫酷!

功能實作步驟分析:

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,
  );
}      

關于我