Flutter实现绘制芬香小程序二维码海报,并保存到相册

效果展示

芬香小程序二维码

普通链接二维码

准备工作

引入依赖插件

  • qr_flutter: ^3.0.1
  • cached_network_image: ^1.0.0

导入图片处理包

import 'dart:ui' as ui;

实现代码

入口文件 share_content_post.dart

class ShareContentPost extends StatefulWidget {
  String bgUrl;
  String qrImageUrl;

  ShareContentPost({this.bgUrl, this.qrImageUrl});

  @override
  _ShareContentPostState createState() => _ShareContentPostState();
}

class _ShareContentPostState extends State<ShareContentPost> {
  GlobalKey globalKey = GlobalKey();

  Future<void> _capturePng() async {
    // '保存中...'
    RenderRepaintBoundary boundary =
        globalKey.currentContext.findRenderObject();
    ui.Image image =
        await boundary.toImage(pixelRatio: window.devicePixelRatio);
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    print(pngBytes);

    var filePath = await ImagePickerSaver.saveFile(fileData: pngBytes);

    var savedFile = File.fromUri(Uri.file(filePath));
    setState(() {
      Future<File>.sync(() => savedFile);
    });

    // '保存成功'

    NavigatorUtil.goBack(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: MyAppBar(
        centerTitle: "生成海报",
        actionName: "保存到相册",
        onPressed: () {
          _capturePng();
        },
      ),
      body: Center(
        child: Container(
          height: 1334 * 0.4,
          width: 750 * 0.4,
          child: RepaintBoundary(
            key: globalKey,
            child: EnterPostPage(
                bgUrl: widget.bgUrl,
                qrImageUrl: widget.qrImageUrl),
          ),
        ),
      ),
    );
  }
}

绘制组件 post_enter.dart

enum Status { loading, complete }

class MainPainter extends CustomPainter {
  final Background background;
  final MainQR hero;
  final PostAvatar postAvatar;
  final Size size;
  final String url;

  MainPainter(
      {this.background, this.hero, this.url, this.size, this.postAvatar});

  @override
  void paint(Canvas canvas, Size size) {
    background.paint(canvas, size);
    postAvatar.paint(canvas, size);
    hero.paint(canvas, size);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return oldDelegate != this;
  }
}

class EnterPostPage extends StatefulWidget {
  String bgUrl;
  String qrImageUrl;

  EnterPostPage({this.bgUrl, this.qrImageUrl});

  _EnterPostPage createState() => _EnterPostPage();
}

class _EnterPostPage extends State<EnterPostPage>
    with TickerProviderStateMixin {
  Status gameStatus = Status.loading;
  int index = 0;
  Background background;
  MainQR hero;
  PostAvatar postAvatar;

  initState() {
    initPost();
  }

  Widget build(BuildContext context) {
    if (gameStatus == Status.loading) {
      return ColorLoader();
    }
    return CustomPaint(
        painter: MainPainter(
            background: background,
            hero: hero,
            postAvatar: postAvatar,
            size: Size(750, 1334)),
        size: Size(750, 1334));
  }

  void initPost() async {
    background = new Background(url: widget.bgUrl);
    hero = new MainQR(url: widget.qrImageUrl);
    postAvatar = new PostAvatar();
    await hero.init();
    await postAvatar.init();
    await background.init();
    setState(() {
      gameStatus = Status.complete;
    });
  }
}

背景组件 post_bg.dart

// 背景图
class Background {
  // 屏幕的宽度
  double screenWidth = 750;

  // 屏幕的高度
  double screenHeight = 1334;

  // 加载的背景图片
  ui.Image image;

  String url;

  // 构造函数
  Background({this.url});

  // 初始化, 各种资源
  Future<VoidCallback> init() async {
    image = await Utils.loadImageByProvider(CachedNetworkImageProvider(url));
  }

  // 绘图函数
  paint(Canvas canvas, Size size) async {
    Rect screenWrap =
        Offset(0.0, 0.0) & Size(screenWidth * 0.4, screenHeight * 0.4);
    Paint screenWrapPainter = new Paint();
    screenWrapPainter.color = Colors.red;
    screenWrapPainter.style = PaintingStyle.fill;
    canvas.drawRect(screenWrap, screenWrapPainter);
    canvas.save();
    canvas.scale(0.4, 0.4);
    Paint paint = new Paint();
    canvas.drawImageRect(
        image,
        Offset(0.0, 0.0) &
            Size(image.width.toDouble(), image.height.toDouble()),
        Offset(0.0, 0.0) & Size(screenWidth, screenHeight),
        paint);
    canvas.restore();
  }
}

头像昵称文字绘制组件

class PostAvatar {
  ui.Image image;
  String nickName;

  PostAvatar();

  @override
  void init() async {
    User user = UserManager.currentUser;
    var avatarUrl = user.avatarUrl;
    nickName = user.nickName;
    image =
        await Utils.loadImageByProvider(CachedNetworkImageProvider(avatarUrl));
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.save();
    Paint paint = new Paint();
    canvas.scale(0.35, 0.35);
    print(image.width);


    var textLeft = 180.0;
    ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
      ui.ParagraphStyle(
        textAlign: TextAlign.left,
        fontSize: 36.0,
        textDirection: TextDirection.ltr,
        maxLines: 1,
      ),
    )
      ..pushStyle(
        ui.TextStyle(
            color: Colours.text_black_333,
            textBaseline: ui.TextBaseline.alphabetic),
      )
      ..addText(nickName);
    ui.Paragraph paragraph = paragraphBuilder.build()
      ..layout(ui.ParagraphConstraints(width: 500.0));
    canvas.drawParagraph(paragraph, Offset(textLeft, 1350.0));

    ui.ParagraphBuilder paragraphBuilder2 = ui.ParagraphBuilder(
      ui.ParagraphStyle(
        textAlign: TextAlign.left,
        fontSize: 36.0,
        textDirection: TextDirection.ltr,
        maxLines: 1,
      ),
    )
      ..pushStyle(
        ui.TextStyle(
            color: Colours.text_black_999,
            textBaseline: ui.TextBaseline.alphabetic),
      )
      ..addText('邀您一起加入芬香社交电商');
    ui.Paragraph paragraph2 = paragraphBuilder2.build()
      ..layout(ui.ParagraphConstraints(width: 500.0));
    canvas.drawParagraph(paragraph2, Offset(textLeft, 1390.0));

    ui.ParagraphBuilder paragraphBuilder3 = ui.ParagraphBuilder(
      ui.ParagraphStyle(
        textAlign: TextAlign.left,
        fontSize: 32.0,
        textDirection: TextDirection.ltr,
        maxLines: 1,
      ),
    )
      ..pushStyle(
        ui.TextStyle(
            color: Colours.text_black_999,
            textBaseline: ui.TextBaseline.alphabetic),
      )
      ..addText('京东社交电商战略合作伙伴');
    ui.Paragraph paragraph3 = paragraphBuilder3.build()
      ..layout(ui.ParagraphConstraints(width: 500.0));
    canvas.drawParagraph(paragraph3, Offset(textLeft, 1430.0));


    var radius = image.width.toDouble() / 2;
    var top = 1350.0;
    var left = 30.0;
    canvas.clipRRect(
        RRect.fromRectXY(
            Rect.fromLTWH(
                left, top, image.width.toDouble(), image.width.toDouble()),
            radius,
            radius),
        doAntiAlias: false);
    canvas.drawImageRect(image, Offset(0.0, 0.0) & Size(400, 400),
        Offset(left, top) & Size(400.0, 400.0), paint);
    canvas.restore();
  }
}

二维码绘制组件

class MainQR {
  ui.Image image;
  String url;

  MainQR({this.url});

  @override
  void init() async {
    image = await Utils.loadImageByProvider(CachedNetworkImageProvider(url));
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.save();
    Paint paint = new Paint();
    canvas.scale(0.25, 0.25);
    canvas.drawImageRect(image, Offset(0.0, 0.0) & Size(400, 400),
        Offset(850.0, 1770.0) & Size(400.0, 400.0), paint);

    ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
      ui.ParagraphStyle(
        textAlign: TextAlign.center,
        fontSize: 36.0,
        textDirection: TextDirection.ltr,
        maxLines: 1,
      ),
    )
      ..pushStyle(
        ui.TextStyle(
            color: Colours.text_black_999,
            textBaseline: ui.TextBaseline.alphabetic),
      )
      ..addText("长按识别");
    ui.Paragraph paragraph = paragraphBuilder.build()
      ..layout(ui.ParagraphConstraints(width: 200.0));

    canvas.drawParagraph(paragraph, Offset(885.0, 2050.0));
    canvas.restore();
  }
}

用到的图片转换工具方法

static Future<ui.Image> getImage(String asset) async {
    ByteData data = await rootBundle.load(asset);
    var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  static Future<ui.Image> getImageByQR(String url, {size: 70.0}) async {
    final image = await QrPainter(
      data: url,
      version: QrVersions.auto,
      gapless: false,
    ).toImage(size);
    final a = await image.toByteData(format: ImageByteFormat.png);
    var codec = await ui.instantiateImageCodec(a.buffer.asUint8List());
    FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  static Future<ui.Image> loadImageByProvider(
      ImageProvider provider, {
        ImageConfiguration config = ImageConfiguration.empty,
      }) async {
    Completer<ui.Image> completer = Completer<ui.Image>(); //完成的回调
    ImageStreamListener listener;
    ImageStream stream = provider.resolve(config); //获取图片流
    listener = ImageStreamListener((ImageInfo frame, bool sync) {
      //监听
      final ui.Image image = frame.image;
      completer.complete(image); //完成
      stream.removeListener(listener); //移除监听
    });
    stream.addListener(listener); //添加监听
    return completer.future; //返回
  }

总结

  • 绘制图片前,需要将图片转换为ui.Image,记得导入import ‘dart:ui’ as ui;
  • 绘制海报时,注意大小的跳转,保证图片比例不变
  • 保存图片用到了RepaintBoundary来实现将组件保存为图片功能
  • 其它疑问可加博主微信号tinywangyining沟通,添加时备注【掘金】
<think>我们有两个需求:1.生成二维码;2.将二维码绘制到Canvas上。 由于二维码生成需要算法,我们可以使用已有的库生成二维码数据(比如QR码的矩阵),然后使用CustomPainter在Canvas上绘制。 步骤: 1. 使用一个二维码生成库(如`qr`)来生成二维码的矩阵数据(包含模块的黑白信息)。 2. 创建一个CustomPainter,在其paint方法中使用Canvas绘制二维码。 引用[1][2][3]提供了关于Canvas和Paint的使用方法,我们可以参考这些绘制方法。 具体步骤: 1. 添加依赖:在pubspec.yaml中添加`qr`库(用于生成二维码数据)和`flutter_svg`(可选,如果不需要可以不用)。 注意:我们不需要`qr_flutter`,因为我们要自己绘制。 2. 使用`qr`库生成二维码数据(QrCode实例)。 3. 创建CustomPainter,根据二维码数据(二维数组)在Canvas上绘制小方块。 4. 在需要的地方使用CustomPaint widget。 示例代码: 步骤1:添加依赖 ```yaml dependencies: qr: ^3.0.0 ``` 步骤2:生成二维码数据 ```dart import 'package:qr/qr.dart'; // 生成二维码 final qrCode = QrCode.fromData( data: 'Hello, QR code!', errorCorrectLevel: QrErrorCorrectLevel.H, ); qrCode.make(); // 生成二维码矩阵 ``` 步骤3:创建CustomPainter ```dart class QrPainter extends CustomPainter { final QrCode qrCode; final Color color; final double gap; // 模块之间的间隙(可选) QrPainter({required this.qrCode, this.color = Colors.black, this.gap = 0}); @override void paint(Canvas canvas, Size size) { // 计算每个模块的大小 final moduleSize = size.width / qrCode.moduleCount; final paint = Paint() ..color = color ..style = PaintingStyle.fill; // 遍历每个模块 for (int x = 0; x < qrCode.moduleCount; x++) { for (int y = 0; y < qrCode.moduleCount; y++) { if (qrCode.isDark(y, x)) { // 注意:qr库中,第一个参数是行(y),第二个是列(x) // 绘制一个矩形 final rect = Rect.fromLTWH( x * moduleSize + gap/2, y * moduleSize + gap/2, moduleSize - gap, moduleSize - gap ); canvas.drawRect(rect, paint); } } } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } ``` 步骤4:使用CustomPaint ```dart CustomPaint( size: Size(200, 200), // 设置二维码大小 painter: QrPainter(qrCode: qrCode, color: Colors.black, gap: 1), ) ``` 完整示例: ```dart import 'package:flutter/material.dart'; import 'package:qr/qr.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Canvas绘制二维码')), body: Center( child: CustomPaint( size: Size(200, 200), painter: QrCodePainter(data: 'https://flutter.dev'), ), ), ), ); } } class QrCodePainter extends CustomPainter { final String data; final Color color; final double gap; QrCodePainter({ required this.data, this.color = Colors.black, this.gap = 0, }); @override void paint(Canvas canvas, Size size) { // 生成二维码 final qrCode = QrCode.fromData(data: data, errorCorrectLevel: QrErrorCorrectLevel.H); qrCode.make(); final moduleCount = qrCode.moduleCount; if (moduleCount == 0) return; final moduleSize = size.width / moduleCount; final paint = Paint() ..color = color ..style = PaintingStyle.fill; for (int y = 0; y < moduleCount; y++) { for (int x = 0; x < moduleCount; x++) { if (qrCode.isDark(y, x)) { final rect = Rect.fromLTWH( x * moduleSize + gap/2, y * moduleSize + gap/2, moduleSize - gap, moduleSize - gap, ); canvas.drawRect(rect, paint); } } } } @override bool shouldRepaint(QrCodePainter oldDelegate) { return oldDelegate.data != data || oldDelegate.color != color || oldDelegate.gap != gap; } } ``` 注意: 1. 二维码生成使用了`qr`库,注意`qrCode.isDark(y, x)`中,第一个参数是行(y坐标),第二个参数是列(x坐标)。 2. 我们通过循环遍历每个模块,如果是黑色(isDark为true),则绘制一个矩形。 3. `gap`参数用于控制模块之间的间隙(即留白),可以根据需要调整。 这样,我们就实现了在Canvas上绘制二维码的功能。 如果需要添加中心图标,可以在绘制二维码后,再在中心位置绘制一个图标(使用drawImage或drawCircle等)。但是注意,二维码中心区域需要留出位置(即不绘制黑色模块),这需要修改二维码生成部分,或者覆盖绘制一个白色区域和图标。由于二维码生成生成的矩阵是固定的,我们可以通过覆盖白色矩形和图标来实现。 例如,在绘制完所有黑色模块后,在中心位置绘制一个白色圆形,然后在上面绘制图标: ```dart // 假设在paint方法的最后添加 // 计算中心区域 final centerStart = moduleCount ~/ 2 - 5; // 中心区域大小(假设10x10个模块) final centerSize = 10; // 覆盖白色矩形 final whitePaint = Paint()..color = Colors.white; final centerRect = Rect.fromLTWH( centerStart * moduleSize, centerStart * moduleSize, centerSize * moduleSize, centerSize * moduleSize, ); canvas.drawRect(centerRect, whitePaint); // 然后绘制图标(这里用圆形代替) final iconPaint = Paint()..color = Colors.blue; canvas.drawCircle( Offset(size.width/2, size.height/2), centerSize * moduleSize / 2, iconPaint, ); ``` 但是,这种方法会破坏二维码的原有结构,可能会影响识别。因此,更推荐的做法是在生成二维码数据时,将中心区域设置为空白(这需要修改二维码生成算法或使用支持logo的生成库)。由于二维码的纠错机制(我们使用了高级别的纠错),通常覆盖一个小图标是可以被识别的。 但是,如果我们要自己控制,可以在生成二维码数据后,手动将中心区域的模块设置为白色(即不绘制)。但是这样可能会破坏二维码,所以需要谨慎。 另一种思路:使用`qr_flutter`库生成二维码,然后将其转换为图片,再在Canvas上绘制图片。但是这样不符合直接绘制到Canvas的需求。 因此,如果必须使用Canvas绘制且需要中心图标,建议采用覆盖绘制的方式,测试二维码的识别性。 由于时间关系,这里不再展开中心图标的绘制。以上代码提供了在Canvas上绘制二维码的基本方法。 引用说明:在绘制过程中,我们使用了Canvas的drawRect方法,以及Paint对象,这些方法在引用[1][2][3]中都有提及[^1][^2][^3]。</think>在 Flutter生成二维码直接绘制到 Canvas 上,可以通过结合 `qr` 库生成二维码数据和使用 `CustomPainter` 实现。以下是详细步骤和代码示例: ### 完整解决方案 ```dart import 'package:flutter/material.dart'; import 'package:qr/qr.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Canvas绘制二维码')), body: Center( child: CustomPaint( size: Size(250, 250), painter: QrCodePainter( data: 'https://flutter.dev', color: Colors.blue[800]!, ), ), ), ), ); } } class QrCodePainter extends CustomPainter { final String data; final Color color; final double gap; // 模块间隙 QrCodePainter({ required this.data, this.color = Colors.black, this.gap = 1.0, }); @override void paint(Canvas canvas, Size size) { // 1. 生成二维码数据矩阵 final qrCode = QrCode.fromData( data: data, errorCorrectLevel: QrErrorCorrectLevel.H, ); qrCode.make(); // 2. 计算模块尺寸 final moduleCount = qrCode.moduleCount; final moduleSize = size.width / moduleCount; // 3. 创建画笔 final paint = Paint() ..color = color ..style = PaintingStyle.fill ..isAntiAlias = true; // 启用抗锯齿[^3] // 4. 绘制二维码模块 for (int y = 0; y < moduleCount; y++) { for (int x = 0; x < moduleCount; x++) { if (qrCode.isDark(y, x)) { final rect = Rect.fromLTWH( x * moduleSize + gap / 2, y * moduleSize + gap / 2, moduleSize - gap, moduleSize - gap, ); // 使用圆角矩形更美观 canvas.drawRRect( RRect.fromRectAndRadius(rect, Radius.circular(moduleSize / 4)), paint, ); } } } // 5. 可选:添加中心图标 final centerSize = moduleSize * 7; final centerRect = Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: centerSize, height: centerSize, ); // 绘制白色背景 canvas.drawRRect( RRect.fromRectAndRadius(centerRect, Radius.circular(centerSize / 4)), Paint()..color = Colors.white, ); // 绘制中心图标(示例使用圆形) canvas.drawCircle( centerRect.center, centerSize / 3, Paint()..color = Colors.blue, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } ``` ### 关键步骤说明 1. **生成二维码数据矩阵**: ```dart final qrCode = QrCode.fromData( data: '二维码内容', errorCorrectLevel: QrErrorCorrectLevel.H, ); qrCode.make(); ``` 2. **计算模块尺寸**: ```dart final moduleCount = qrCode.moduleCount; final moduleSize = size.width / moduleCount; ``` 3. **绘制二维码模块**: ```dart if (qrCode.isDark(y, x)) { canvas.drawRRect( RRect.fromRectAndRadius(rect, Radius.circular(moduleSize / 4)), paint, ); } ``` 4. **添加中心图标**: ```dart // 绘制白色背景 canvas.drawRRect(centerRect, Paint()..color = Colors.white); // 绘制图标 canvas.drawCircle(center, radius, Paint()..color = Colors.blue); ``` ### 自定义选项 1. **调整样式**: ```dart // 圆角效果 canvas.drawRRect(RRect.fromRectAndRadius(rect, radius), paint); // 渐变效果 paint.shader = LinearGradient( colors: [Colors.blue, Colors.green], ).createShader(rect); ``` 2. **添加背景图**: ```dart // 加载图片 final image = await loadImageFromAsset('assets/background.png'); // 绘制背景 canvas.drawImageRect( image, Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), Rect.fromLTWH(0, 0, size.width, size.height), Paint(), ); ``` 3. **动态数据**: ```dart class QrCodePainter extends CustomPainter { QrCodePainter({required this.data}); final String data; // 外部传入动态数据 @override bool shouldRepaint(QrCodePainter old) => old.data != data; } ``` ### 性能优化建议 1. **避免重复计算**: ```dart @override void paint(Canvas canvas, Size size) { if (_qrCode == null || _lastData != data) { _qrCode = QrCode.fromData(data: data); _qrCode!.make(); _lastData = data; } // ...绘制代码 } ``` 2. **使用 `RepaintBoundary`**: ```dart RepaintBoundary( child: CustomPaint( painter: QrCodePainter(data: dynamicData), ), ) ``` 这种方法直接操作 Canvas,提供了完全的绘制控制权,适合需要高度自定义的场景[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值