效果展示
准备工作
引入依赖插件
- 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
沟通,添加时备注【掘金】