Flutter 打印功能

本文,我们来讲讲,如何通过 Flutter 实现调其打印机🖨️打印的功能。

开发环境

  • Flutter Version:3.16.4
  • 系统:macOS Sonoma - Apple M1 芯片
  • Android Studio: 17.0.7

我们通过 flutter create project_name 创建项目。

我们如何打印

关于调起 printer 打印的功能。我们有以下的想法:

  1. 打印当前路由页面的内容,类似于网页的调用 window.print 方式打印
  2. 打印页面中指定的 widget 的内容
  3. 打印重组的 widget 的内容
  4. 将页面指定的 widget 转化为 image 之后,再调起打印

针对第一点,我们并没有发现在 app 中有类似 window.print 的方法;而对第二点,我们也不能指定页面中 widget 进行打印。剩下的第三点和第四点,我们都可以实现。

接下来,我们将应用 flutter printing 包,来演示后两种实现方式。

引入 printing 包

引入 printing 很简单:

  1. 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_flutterflutter_inappwebview 是可选,笔者在调试 macos 的项目时候用到。printing 在编写本文时候的版本是 ^5.12.0,请以 官网 版本为主

  1. 然后,我们可以通过 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';

上面引入 pdfprinting 相关包。

因为我们是在 macos 上进行调试,我们还需要在 macos/Runner/Release.entitlementsmacos/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), // 边距
  ...
)

✅,谢谢阅读 🌹

相关推荐
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
想用offer打牌8 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX9 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法10 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
renke336410 小时前
Flutter for OpenHarmony:色彩捕手——基于HSL色轮与感知色差的交互式色觉训练系统
flutter