鸿蒙+flutter 跨平台开发——支持自定义的打印纸生成器实战

🚀运行效果展示

在这里插入图片描述
在这里插入图片描述

🎯 前言

在移动应用开发领域,跨平台技术已经成为趋势。Flutter作为Google推出的UI框架,以其"Write Once, Run Anywhere"的理念,深受开发者喜爱。而鸿蒙系统作为华为自主研发的分布式操作系统,也在快速发展。本文将介绍如何使用Flutter框架开发一款支持鸿蒙系统的自定义打印纸生成器,包括多种纸张类型、自定义颜色、网格线、文本编辑和打印模拟等功能。

打印纸生成器作为一种实用工具,可以帮助用户快速创建各种类型的打印纸,如A4纸、收据纸和标签纸等,具有广泛的应用场景。

📱 项目介绍

功能特点

  • 多种打印纸类型:支持A4纸、收据纸和标签纸
  • 🎨 自定义纸张颜色:可选择白色、浅灰、米黄和浅蓝等颜色
  • 📏 网格线显示:支持开关网格线,方便对齐和排版
  • ✏️ 文本编辑功能:支持多行文本输入,实时更新打印预览
  • 🖨️ 打印模拟效果:具有打印动画和进度显示
  • 💾 保存和分享:支持保存当前配置和分享打印内容

技术栈

  • Flutter:UI框架
  • Dart:开发语言
  • 鸿蒙系统:运行平台
  • 状态管理:使用StatefulWidget和AnimationController
  • 自定义绘制:使用CustomPaint实现撕边效果

🔧 核心功能实现

多种打印纸类型

打印纸生成器支持三种不同类型的打印纸:

纸张类型 宽度(mm) 高度(mm) 边距(mm)
A4纸 210 297 20
收据纸 80 300 10
标签纸 100 150 5

每种纸张类型都有其特定的尺寸和边距,用户可以通过下拉菜单进行选择。

自定义颜色选择

用户可以选择四种不同的纸张颜色:

  • 白色
  • 浅灰
  • 米黄
  • 浅蓝

颜色选择通过弹出对话框实现,用户点击颜色选项即可切换纸张颜色。

文本编辑功能

打印纸生成器支持多行文本输入,用户可以在文本框中输入要打印的内容,实时更新打印预览。文本框支持以下功能:

  • 多行输入
  • 支持项目符号
  • 自动换行
  • 实时预览更新

打印模拟效果

打印模拟功能包括:

  • 打印动画:使用AnimationController实现平滑的打印进度动画
  • 进度显示:LinearProgressIndicator显示打印进度
  • 完成提示:打印完成后显示SnackBar提示

撕边效果实现

撕边效果使用CustomPaint自定义绘制实现,通过Path绘制波浪形撕边效果,增强打印纸的真实感。

📊 项目架构

📦 lib
├── 📁 screens
│   ├── 📄 print_paper_simulator_screen.dart  # 打印纸模拟器主页面
│   └── 📄 multi_function_flip_clock_screen.dart  # 多功能翻页时钟页面
└── 📄 main.dart  # 应用入口

🌟 核心流程

打印纸生成流程

选择纸张类型

选择颜色

开关网格线

编辑文本

点击打印

初始化应用

显示打印纸预览

等待用户操作

更新纸张尺寸

更新纸张颜色

更新网格线显示

更新打印内容

开始打印动画

显示打印进度

打印完成

显示完成提示

打印模拟流程

点击打印按钮

设置打印状态为true

重置动画控制器

启动动画控制器

更新打印进度

动画是否结束?

设置打印状态为false

显示打印完成提示

💻 代码展示

打印纸类型定义

/// 打印纸类型枚举
enum PaperType {
  /// A4纸
  a4,
  /// 收据纸
  receipt,
  /// 标签纸
  label,
}

打印纸预览构建

/// 构建打印纸预览
Widget _buildPaperPreview() {
  // 初始化默认值
  double paperWidth = 210.0;
  double paperHeight = 297.0;
  double margin = 20.0;

  // 根据不同纸张类型设置尺寸
  switch (_currentPaperType) {
    case PaperType.a4:
      paperWidth = 210.0; // A4宽度,单位:mm
      paperHeight = 297.0;
      margin = 20.0;
      break;
    case PaperType.receipt:
      paperWidth = 80.0; // 收据宽度,单位:mm
      paperHeight = 300.0;
      margin = 10.0;
      break;
    case PaperType.label:
      paperWidth = 100.0; // 标签宽度,单位:mm
      paperHeight = 150.0;
      margin = 5.0;
      break;
  }

  // 转换为逻辑像素
  final scale = 3.8; // 近似转换:1mm ≈ 3.8像素
  final width = paperWidth * scale;
  final height = paperHeight * scale;
  final padding = margin * scale;

  return Stack(
    alignment: Alignment.center,
    children: [
      Container(
        width: width,
        decoration: BoxDecoration(
          color: _paperColor,
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.1),
              blurRadius: 5,
              offset: const Offset(0, 2),
            ),
          ],
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 打印纸顶部
            Container(
              width: width,
              height: height,
              padding: EdgeInsets.all(padding),
              decoration: _showGridLines
                  ? BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.topLeft,
                        end: Alignment.bottomRight,
                        colors: [
                          _paperColor,
                          _paperColor.withOpacity(0.98),
                        ],
                        stops: const [0.0, 1.0],
                      ),
                      border: Border.all(
                        color: Colors.grey.withOpacity(0.1),
                        width: 0.5,
                      ),
                    )
                  : null,
              child: Text(
                _textContent,
                style: const TextStyle(
                  fontSize: 14,
                  height: 1.5,
                ),
              ),
            ),
            // 打印纸底部(撕边效果)
            Container(
              width: width,
              height: 20,
              child: CustomPaint(
                painter: TearEdgePainter(),
                size: Size(width, 20),
              ),
            ),
          ],
        ),
      ),
      // 打印进度指示器
      if (_isPrinting)
        Container(
          width: width * 0.8,
          height: 40,
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.9),
            borderRadius: BorderRadius.circular(20),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.2),
                blurRadius: 5,
                offset: const Offset(0, 2),
              ),
            ],
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '正在打印...',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 4),
              LinearProgressIndicator(
                value: _printProgress,
                backgroundColor: Colors.grey.withOpacity(0.3),
                valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
                minHeight: 6,
                borderRadius: BorderRadius.circular(3),
              ),
            ],
          ),
        ),
    ],
  );
}

撕边效果画笔

/// 撕边效果画笔
class TearEdgePainter extends CustomPainter {
  
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.grey.withOpacity(0.3)
      ..strokeWidth = 1.0
      ..style = PaintingStyle.fill;

    final path = Path();
    path.moveTo(0, 0);

    // 绘制波浪形撕边效果
    final waveCount = (size.width / 20).round();
    final waveHeight = size.height;

    for (int i = 0; i <= waveCount; i++) {
      final x = i * 20.0;
      final y = i % 2 == 0 ? 0.0 : waveHeight;
      path.lineTo(x, y);
    }

    path.lineTo(size.width, 0);
    path.close();

    canvas.drawPath(path, paint);
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

打印模拟功能

/// 模拟打印
void _simulatePrint() {
  setState(() {
    _isPrinting = true;
    _printProgress = 0.0;
  });

  // 重置并启动动画控制器
  _animationController.reset();
  _animationController.forward().then((_) {
    // 动画结束后,显示打印完成提示
    setState(() {
      _isPrinting = false;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('打印完成!')),
    );
  });
}

📱 界面设计

主界面

打印纸模拟器主界面包括以下部分:

  1. 顶部导航栏:显示应用名称,包含保存和分享按钮
  2. 设置选项栏:包含纸张类型选择、颜色选择、网格线开关和打印按钮
  3. 打印纸预览区:显示当前纸张预览,包括文本内容、颜色和网格线
  4. 文本编辑区:支持多行文本输入,实时更新打印预览

交互设计

  • 纸张类型选择:通过下拉菜单实现,实时更新纸张尺寸
  • 颜色选择:通过弹出对话框实现,点击颜色选项即可切换
  • 网格线开关:通过Switch组件实现,实时更新网格线显示
  • 文本编辑:通过TextField组件实现,实时更新打印内容
  • 打印按钮:点击后开始打印动画,显示进度条

🛠️ 技术难点和解决方案

1. 多种纸张类型的实现

难点:不同纸张类型具有不同的尺寸和边距,需要动态调整界面布局。

解决方案:使用枚举定义不同纸张类型,根据选择的类型动态计算纸张尺寸和边距,并更新界面显示。

2. 撕边效果的实现

难点:实现真实的撕边效果,增强打印纸的真实感。

解决方案:使用CustomPaint自定义绘制,通过Path绘制波浪形撕边效果,模拟真实的纸张撕边。

3. 打印模拟动画

难点:实现平滑的打印动画和进度显示。

解决方案:使用AnimationController和LinearProgressIndicator,结合SnackBar实现完整的打印模拟效果。

4. 实时预览更新

难点:文本编辑时实时更新打印预览,保证界面流畅性。

解决方案:使用setState和TextField的onChanged回调,实时更新文本内容并刷新界面。

🎉 总结

项目成果

本项目成功实现了一个支持自定义的打印纸生成器,具有以下特点:

  • ✅ 支持多种打印纸类型(A4纸、收据纸、标签纸)
  • ✅ 支持自定义纸张颜色
  • ✅ 支持网格线显示开关
  • ✅ 支持多行文本编辑
  • ✅ 具有打印模拟效果
  • ✅ 实现了真实的撕边效果

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐