popeyelau / wiki

📒Wiki for many useful notes, source, commands and snippets.
2 stars 0 forks source link

Flutter 绘制分享海报 #29

Open popeyelau opened 3 years ago

popeyelau commented 3 years ago

poster_painter.dart

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'dart:math' as math show max;
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cache;

import 'utils.dart';

class PosterPainter {
  /// 商品海报
  /// [coverUrl] 商品图
  /// [miniCodeUrl] 小程序码
  /// [title] 商品名称
  /// [price] 价格
  /// [mktPrice] 市场价
  /// [saleDate] 预售日期
  /// [store] 门店
  static Future<ByteData> drawPoster(
      {String coverUrl,
      String miniCodeUrl,
      String title,
      double price,
      double mktPrice,
      String saleDate,
      String store}) async {
    final imageUrls = <String>[
      "https://front-xps-cdn.xsyx.xyz/2020/01/10/1468273837.png", //background
      coverUrl, //cover
      miniCodeUrl, //miniCode
    ];

    final downloadManager = cache.DefaultCacheManager();

    bool hasError = false;
    final files = await Future.wait<cache.FileInfo>(
            imageUrls.map(downloadManager.downloadFile).toList())
        .catchError((e, s) {
      hasError = true;
      debugPrint(e);
    });

    if (hasError || files.length != imageUrls.length) return null;

    final background = await Utils.loadImage(files[0].file.readAsBytesSync());
    final cover = await Utils.loadImage(files[1].file.readAsBytesSync());
    final miniCode = await Utils.loadImage(files[2].file.readAsBytesSync());

    final canvasSize =
        Size(background.width.toDouble(), background.height.toDouble());
    final coverSize = Size(cover.width.toDouble(), cover.height.toDouble());
    final miniCodeSize =
        Size(miniCode.width.toDouble(), miniCode.height.toDouble());

    final recorder = PictureRecorder();
    final canvas = Canvas(recorder);
    final painter = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white;

    canvas.drawImage(background, Offset.zero, painter);

    double scaledSize = math.max(coverSize.width, coverSize.height) * 0.75;
    double x = (canvasSize.width - scaledSize) * 0.5;
    double y = 100.0;

    canvas.drawImageRect(
        cover,
        Rect.fromLTRB(0, 0, coverSize.width, coverSize.height),
        Rect.fromLTWH(x, y, scaledSize, scaledSize),
        painter);

    canvas.drawImageRect(
        miniCode,
        Rect.fromLTRB(0, 0, miniCodeSize.width, miniCodeSize.height),
        Rect.fromLTWH(x, canvasSize.height - 160, 150, 150),
        painter);

    final style = TextStyle(
        fontSize: 32, color: Colors.black, fontWeight: FontWeight.normal);
    final textWidth = canvasSize.width - x * 2;

    TextPainter(
      textAlign: TextAlign.left,
      text: TextSpan(children: [
        TextSpan(
            text: "$title\n",
            style: style.copyWith(height: 2, fontWeight: FontWeight.w500)),
        TextSpan(
            text: "¥$price  ",
            style: style.copyWith(
                fontSize: 40, color: Colors.red, fontWeight: FontWeight.w600)),
        TextSpan(
            text: "¥$mktPrice",
            style: style.copyWith(
                decoration: TextDecoration.lineThrough, color: Colors.grey)),
        TextSpan(
            text: "  预售时间: $saleDate\n",
            style: style.copyWith(color: Colors.grey.shade400)),
        TextSpan(
            text: "提货门店: $store",
            style: style.copyWith(height: 2, color: Colors.orange))
      ]),
      textDirection: TextDirection.ltr,
    )
      ..layout(maxWidth: textWidth, minWidth: textWidth)
      ..paint(canvas, Offset(x, y + scaledSize));

    final picture = recorder.endRecording();
    final img = await picture.toImage(
        canvasSize.width.toInt(), canvasSize.height.toInt());
    return img.toByteData(format: ImageByteFormat.png);
  }
}

utils.dart

import 'dart:async';
import 'dart:ui';

class Utils {
  static Future<Image> loadImage(List<int> img) async {
    final Completer<Image> completer = Completer();
    decodeImageFromList(img, (Image img) {
      return completer.complete(img);
    });
    return completer.future;
  }
}

home_page.dart

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_draw_poster/poster_painter.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  ByteData posterByteData;

  @override
  void initState() {
    super.initState();
    drawing();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xffcccccc),
      appBar: AppBar(
        title: Text('Drawing'),
      ),
      body: Container(
          padding: EdgeInsets.all(20),
          alignment: Alignment.center,
          child: poster),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.insert_photo),
        onPressed: drawing,
      ),
    );
  }

  Widget get poster {
    if (posterByteData == null) return null;
    return Image.memory(Uint8List.view(posterByteData.buffer));
  }

  Future<void> drawing() async {
    final bytes = await PosterPainter.drawPoster(
        coverUrl: "https://front-xps-cdn.xsyx.xyz/2020/12/04/241721671.jpg",
        miniCodeUrl: "https://front-xps-cdn.xsyx.xyz/2020/12/04/1348955141.jpg",
        title: "精卫 精品红提 230g/盒 正负 20g",
        price: 2.99,
        mktPrice: 5.99,
        saleDate: "12月12日",
        store: "麓谷总部店");
    setState(() {
      posterByteData = bytes;
    });
  }
}

image