本文,我们来讲讲,如何通过 Flutter
实现调其打印机🖨️打印的功能。
开发环境
Flutter Version
:3.16.4- 系统:
macOS Sonoma
-Apple M1
芯片 Android Studio
: 17.0.7
我们通过 flutter create project_name
创建项目。
我们如何打印
关于调起 printer
打印的功能。我们有以下的想法:
- 打印当前路由页面的内容,类似于网页的调用
window.print
方式打印 - 打印页面中指定的
widget
的内容 - 打印重组的
widget
的内容 - 将页面指定的
widget
转化为image
之后,再调起打印
针对第一点,我们并没有发现在 app
中有类似 window.print
的方法;而对第二点,我们也不能指定页面中 widget
进行打印。剩下的第三点和第四点,我们都可以实现。
接下来,我们将应用 flutter printing
包,来演示后两种实现方式。
引入 printing 包
引入 printing 很简单:
- 将
printing
包添加到我们的pubspec.yaml
文件:
yaml
dependencies:
flutter:
sdk: flutter
webview_flutter: ^2.0.13 # optional
flutter_inappwebview: ^5.3.2 # optional
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
printing: ^5.12.0
webview_flutter
和 flutter_inappwebview
是可选,笔者在调试 macos
的项目时候用到。printing
在编写本文时候的版本是 ^5.12.0
,请以 官网 版本为主
- 然后,我们可以通过
flutter pub get
来获取包
打印组合的 widgets
下面,我们以一个简单的案例来说说怎么使用该包,并怎么打印组合的 widget
。
我们直接在项目的 main.dart
上操作:
dart
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
上面引入 pdf
和 printing
相关包。
因为我们是在 macos
上进行调试,我们还需要在 macos/Runner/Release.entitlements
和 macos/Runner/DebugProfile.entitlements
文件中添加内容:
bash
<key>com.apple.security.print</key>
<true/>
如果是其他平台开发调试,请参考 printing 引入相关的内容。
之后我们在 main.dart
中实现相关的逻辑:
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Print Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: _printPdf,
child: Text('Print'),
),
),
);
}
上面我们编写了相关的 widget
,展示一个 Print
按钮,当点击按钮时候,触发方法 _printPdf
,该方法的实现如下👇
dart
Future<void> _printPdf() async {
try {
final doc = pw.Document();
doc.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Center(
child: pw.Text('Hello Jimmy'),
);
}
));
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => doc.save(),
);
} catch (e) {
print(e);
}
}
在这个方法中,我们在 addPage
中重新组合了需要打印的 widgets
,然后调起打印机 Printing.layoutPdf
,动态如下👇
那么,对于复杂的内容,如果我们还是编写自定义的 widgets
的话,那不切实际,维护成本高。那么,我们有什么方法打印它呢?这就是下面我们要介绍的了~
widgets 内容转 image,再打印 image
我们直接将页面上的 widgets
内容转换为 image
,再结合上面提及的打印组合的 widgets
处理即可。
将 widgets 内容转 image
先上代码:
dart
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey boundaryKey = GlobalKey();
Uint8List _imageBytes = Uint8List(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Widget to Image Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RepaintBoundary(
key: boundaryKey,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
'Hello, Jimmy!',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _capturePng,
child: Text('Capture Image'),
),
SizedBox(height: 20),
if (!_imageBytes.isEmpty)
Image.memory(
_imageBytes,
width: 200,
height: 200,
),
],
),
),
);
}
Future<void> _capturePng() async {
try {
RenderRepaintBoundary? boundary =
boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
ui.Image? image = await boundary?.toImage(pixelRatio: 3.0);
ByteData? byteData =
await image?.toByteData(format: ui.ImageByteFormat.png);
setState(() {
_imageBytes = byteData!.buffer.asUint8List(); // 赋值
});
} catch (e) {
print(e);
}
}
}
在代码中,我们用 RepaintBoundary
来指定了重绘的区域为 200*200
的文本值。当我们点击 ElevatedButton
挂件时候,会触发 _capturePng
方法。在 _capturePng
方法中,我们将区域内的内容转换为图像,并且,将图像转为位数据,给 _imageBytes
赋值,展现在页面上。相关 Gif
图如下👇
整合 Image 挂件
在上面的例子中,我们保存了生成的图数据。接下来,我们将该图片打印出来。上面的代码,我们在原始基础上更改:
dart
ElevatedButton(
onPressed: () => _capturePng(context),
child: Text('Capture Image'),
),
引入包:
dart
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
然后补充 _capturePng
方法:
dart
Future<void> _capturePng(BuildContext ctx) async {
try {
// 添加 print
final doc = pw.Document();
RenderRepaintBoundary? boundary = boundaryKey.currentContext
?.findRenderObject() as RenderRepaintBoundary?;
ui.Image? image = await boundary?.toImage(pixelRatio: 3.0);
ByteData? byteData =
await image?.toByteData(format: ui.ImageByteFormat.png);
final pageFormat = PdfPageFormat.a4;
print(MediaQuery.of(ctx).size.height); // 测试打印界面的高度
doc.addPage(pw.Page(
pageFormat: pageFormat,
orientation: pw.PageOrientation.landscape,
build: (pw.Context context) {
return pw.Center(
child: pw.Image( // 图像挂件
pw.MemoryImage(_imageBytes),
width: pageFormat.height - 20,
fit: pw.BoxFit.fitWidth,
),
);
}));
// 打印
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => doc.save(),
);
} catch (e) {
print(e);
}
}
上面,我们通过 pw.MemoryImage(_imageBytes)
指定 Image
的内容,并调起打印机🖨️打印~
为了方便演示,看到边界,我们更改了下 UI
当然,我们可以设定其打印的边距和指定内容的方向等:
dart
pw.Page(
orientation: pw.PageOrientation.landscape, // 内容的方向
margin: pw.EdgeInsets.all(16.0), // 边距
...
)
✅,谢谢阅读 🌹