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

效果展示

Flutter实现绘制芬香小程序二维码海报,并保存到相册_第1张图片

Flutter实现绘制芬香小程序二维码海报,并保存到相册_第2张图片

准备工作

引入依赖插件

  • 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 {
  GlobalKey globalKey = GlobalKey();

  Future _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.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
    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 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 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 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 loadImageByProvider(
      ImageProvider provider, {
        ImageConfiguration config = ImageConfiguration.empty,
      }) async {
    Completer completer = Completer(); //完成的回调
    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沟通,添加时备注【掘金】

你可能感兴趣的:(Flutter)