Flutter 二维码生成的鸿蒙化适配与实战指南

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


各位小伙伴们好呀!👋 我是那个上海某高校的大一计算机学生,继续来给大家分享 Flutter for OpenHarmony 开发的学习心得!

今天要聊的是 二维码生成功能!📱📱📱

二维码现在真的太常见了:

  • 🛒 商品码:扫一扫了解商品详情
  • 📲 分享码:分享 App 给朋友
  • 🔐 支付码:扫码支付
  • 📶 WiFi 码:扫一扫连 WiFi

今天就给大家详细分享一下如何用 Flutter 生成漂亮的二维码!


一、功能引入介绍 📱

1.1 二维码的应用场景

场景 说明 示例
商品码 标识商品信息 淘宝商品码
联系码 分享联系方式 微信名片
支付码 扫码支付 支付宝/微信支付
WiFi 码 分享 WiFi 餐厅 WiFi
小程序码 打开小程序 微信小程序
URL 码 跳转网页 活动链接

1.2 Flutter 二维码方案

插件 优点 缺点
qr_flutter 功能强大、样式丰富 ✅ -
qr_code_scanner 支持扫描 需要相机权限
mobile_scanner 新版相机插件 配置复杂

我们选择 qr_flutter


二、环境与依赖配置 🔧

2.1 pubspec.yaml 依赖

dependencies:
  flutter:
    sdk: flutter
  
  # ========== 二维码 ==========
  qr_flutter: ^4.1.0
  
  # ========== 分享 ==========
  share_plus: ^10.1.4
  
  # ========== 文件 ==========
  path_provider: ^2.1.4
  
  # ========== 动画 ==========
  flutter_animate: ^4.5.0

三、分步实现完整代码 🚀

3.1 二维码页面完整实现

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:flutter_animate/flutter_animate.dart';

/// 二维码生成页面
/// 
/// 支持:
/// - 生成任意内容的二维码
/// - 自定义颜色
/// - 复制链接
/// - 分享二维码图片
class QRCodePage extends StatefulWidget {
  final String? initialData;
  final String? title;

  const QRCodePage({
    super.key,
    this.initialData,
    this.title,
  });

  
  State<QRCodePage> createState() => _QRCodePageState();
}

class _QRCodePageState extends State<QRCodePage> {
  /// 文本控制器
  final TextEditingController _dataController = TextEditingController();
  
  /// 当前二维码内容
  String _currentData = '';
  
  /// 选中的颜色
  Color _selectedColor = const Color(0xFF6366F1);

  /// 可选颜色列表
  final List<Color> _availableColors = [
    const Color(0xFF6366F1),  // 靛蓝
    const Color(0xFF8B5CF6),  // 紫色
    const Color(0xFFEC4899),  // 粉色
    const Color(0xFFEF4444),  // 红色
    const Color(0xFFF97316),  // 橙色
    const Color(0xFF10B981),  // 绿色
    const Color(0xFF06B6D4),  // 青色
    const Color(0xFF3B82F6),  // 蓝色
    const Color(0xFF1E293B),  // 深灰
    Colors.black,
  ];

  
  void initState() {
    super.initState();
    if (widget.initialData != null) {
      // 有初始数据,使用初始数据
      _currentData = widget.initialData!;
      _dataController.text = widget.initialData!;
    } else {
      // 使用默认数据
      _currentData = 'https://my-ohos-app.com';
      _dataController.text = _currentData;
    }
  }

  
  void dispose() {
    _dataController.dispose();
    super.dispose();
  }

  /// 更新二维码
  void _updateQRCode() {
    setState(() {
      _currentData = _dataController.text.trim();
      if (_currentData.isEmpty) {
        _currentData = 'https://my-ohos-app.com';
      }
    });
  }

  /// 分享二维码
  void _shareQRCode() async {
    if (_currentData.isEmpty) return;

    try {
      // 创建二维码图片
      final qrPainter = QrPainter(
        data: _currentData,
        version: QrVersions.auto,
        // 眼睛样式
        eyeStyle: QrEyeStyle(
          eyeShape: QrEyeShape.square,
          color: _selectedColor,
        ),
        // 数据模块样式
        dataModuleStyle: QrDataModuleStyle(
          dataModuleShape: QrDataModuleShape.square,
          color: _selectedColor,
        ),
      );

      // 转换为图片数据
      final picData = await qrPainter.toImageData(300);
      if (picData == null) return;

      // 保存到临时文件
      final tempDir = await getTemporaryDirectory();
      final file = File('${tempDir.path}/qr_code.png');
      await file.writeAsBytes(picData.buffer.asUint8List());

      // 分享
      await Share.shareXFiles(
        [XFile(file.path)],
        text: '扫描二维码: $_currentData',
      );
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('分享失败: $e')),
        );
      }
    }
  }

  /// 复制到剪贴板
  void _copyToClipboard() {
    Clipboard.setData(ClipboardData(text: _currentData));
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('已复制到剪贴板'),
        behavior: SnackBarBehavior.floating,
        duration: Duration(seconds: 1),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF8FAFC),
      
      // AppBar
      appBar: AppBar(
        backgroundColor: Colors.white,
        elevation: 1,
        title: Text(
          widget.title ?? '生成二维码',
          style: const TextStyle(
            color: Color(0xFF1E293B),
            fontWeight: FontWeight.w600,
          ),
        ),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back, color: Color(0xFF1E293B)),
          onPressed: () => Navigator.pop(context),
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.share, color: Color(0xFF1E293B)),
            onPressed: _shareQRCode,
          ),
        ],
      ),
      
      // 页面主体
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // ============ 1. 二维码预览 ============
            _buildQRPreview()
                .animate()
                .fadeIn(duration: 300.ms)
                .scale(begin: const Offset(0.9, 0.9), end: const Offset(1, 1)),
            
            const SizedBox(height: 24),
            
            // ============ 2. 内容输入 ============
            _buildDataInput()
                .animate()
                .fadeIn(delay: 100.ms, duration: 300.ms)
                .slideY(begin: 0.2, end: 0),
            
            const SizedBox(height: 24),
            
            // ============ 3. 颜色选择器 ============
            _buildColorPicker()
                .animate()
                .fadeIn(delay: 200.ms, duration: 300.ms)
                .slideY(begin: 0.2, end: 0),
            
            const SizedBox(height: 24),
            
            // ============ 4. 快捷操作 ============
            _buildQuickActions()
                .animate()
                .fadeIn(delay: 300.ms, duration: 300.ms)
                .slideY(begin: 0.2, end: 0),
          ],
        ),
      ),
    );
  }

  /// 二维码预览组件
  Widget _buildQRPreview() {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.05),
            blurRadius: 20,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        children: [
          // 二维码容器
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(16),
              border: Border.all(color: Colors.grey.shade200),
            ),
            child: QrImageView(
              data: _currentData,
              version: QrVersions.auto,  // 自动版本
              size: 200,                // 二维码大小
              backgroundColor: Colors.white,
              
              // 眼睛样式(四个角落的方块)
              eyeStyle: QrEyeStyle(
                eyeShape: QrEyeShape.square,  // 方形眼睛
                color: _selectedColor,
              ),
              
              // 数据模块样式(小方块)
              dataModuleStyle: QrDataModuleStyle(
                dataModuleShape: QrDataModuleShape.square,
                color: _selectedColor,
              ),
            ),
          ),
          
          const SizedBox(height: 16),
          
          // 内容摘要
          Text(
            _currentData.length > 30 
                ? '${_currentData.substring(0, 30)}...' 
                : _currentData,
            style: TextStyle(
              color: Colors.grey[600],
              fontSize: 12,
            ),
            textAlign: TextAlign.center,
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
        ],
      ),
    );
  }

  /// 数据输入组件
  Widget _buildDataInput() {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: _cardDecoration(),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 标题
          const Row(
            children: [
              Icon(Icons.edit_note, color: Color(0xFF6366F1), size: 20),
              SizedBox(width: 8),
              Text(
                '内容',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF1E293B),
                ),
              ),
            ],
          ),
          
          const SizedBox(height: 12),
          
          // 输入框
          TextField(
            controller: _dataController,
            maxLines: 3,
            decoration: InputDecoration(
              hintText: '输入文本、链接或任意数据',
              hintStyle: TextStyle(color: Colors.grey[400]),
              filled: true,
              fillColor: const Color(0xFFF8FAFC),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(12),
                borderSide: BorderSide.none,
              ),
              focusedBorder: OutlineInputBorder(
                borderRadius: BorderRadius.circular(12),
                borderSide: const BorderSide(
                  color: Color(0xFF6366F1),
                  width: 2,
                ),
              ),
              contentPadding: const EdgeInsets.all(16),
            ),
            onChanged: (_) => _updateQRCode(),
          ),
          
          const SizedBox(height: 12),
          
          // 快捷输入按钮
          _buildQuickDataButtons(),
        ],
      ),
    );
  }

  /// 快捷数据按钮
  Widget _buildQuickDataButtons() {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: [
        _buildQuickChip('商品链接', () {
          _dataController.text = 'https://shop.example.com/product/12345';
          _updateQRCode();
        }),
        _buildQuickChip('邀请码', () {
          _dataController.text = 'INVITE:ABC123XYZ';
          _updateQRCode();
        }),
        _buildQuickChip('联系方式', () {
          _dataController.text = 'TEL:13800138000';
          _updateQRCode();
        }),
        _buildQuickChip('WiFi', () {
          _dataController.text = 'WIFI:T:WPA;P:password;;';
          _updateQRCode();
        }),
      ],
    );
  }

  /// 快捷标签按钮
  Widget _buildQuickChip(String label, VoidCallback onTap) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
        decoration: BoxDecoration(
          color: const Color(0xFF6366F1).withValues(alpha: 0.1),
          borderRadius: BorderRadius.circular(20),
        ),
        child: Text(
          label,
          style: const TextStyle(
            color: Color(0xFF6366F1),
            fontSize: 12,
            fontWeight: FontWeight.w500,
          ),
        ),
      ),
    );
  }

  /// 颜色选择器
  Widget _buildColorPicker() {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: _cardDecoration(),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Row(
            children: [
              Icon(Icons.palette, color: Color(0xFF6366F1), size: 20),
              SizedBox(width: 8),
              Text(
                '颜色',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF1E293B),
                ),
              ),
            ],
          ),
          
          const SizedBox(height: 16),
          
          // 颜色网格
          Wrap(
            spacing: 12,
            runSpacing: 12,
            children: _availableColors.map((color) {
              final isSelected = _selectedColor == color;
              return GestureDetector(
                onTap: () => setState(() => _selectedColor = color),
                child: Container(
                  width: 44,
                  height: 44,
                  decoration: BoxDecoration(
                    color: color,
                    shape: BoxShape.circle,
                    border: Border.all(
                      color: isSelected ? Colors.white : Colors.transparent,
                      width: 3,
                    ),
                    boxShadow: isSelected
                        ? [
                            BoxShadow(
                              color: color.withValues(alpha: 0.5),
                              blurRadius: 8,
                              spreadRadius: 2,
                            ),
                          ]
                        : null,
                  ),
                  child: isSelected
                      ? const Icon(Icons.check, color: Colors.white, size: 20)
                      : null,
                ),
              );
            }).toList(),
          ),
        ],
      ),
    );
  }

  /// 快捷操作
  Widget _buildQuickActions() {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: _cardDecoration(),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Row(
            children: [
              Icon(Icons.flash_on, color: Color(0xFF6366F1), size: 20),
              SizedBox(width: 8),
              Text(
                '快捷操作',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF1E293B),
                ),
              ),
            ],
          ),
          
          const SizedBox(height: 16),
          
          Row(
            children: [
              // 复制按钮
              Expanded(
                child: _buildActionButton(
                  icon: Icons.copy,
                  label: '复制',
                  onTap: _copyToClipboard,
                ),
              ),
              
              const SizedBox(width: 12),
              
              // 分享按钮
              Expanded(
                child: _buildActionButton(
                  icon: Icons.share,
                  label: '分享',
                  onTap: _shareQRCode,
                  isPrimary: true,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  /// 操作按钮
  Widget _buildActionButton({
    required IconData icon,
    required String label,
    required VoidCallback onTap,
    bool isPrimary = false,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 14),
        decoration: BoxDecoration(
          color: isPrimary 
              ? const Color(0xFF6366F1) 
              : const Color(0xFFF8FAFC),
          borderRadius: BorderRadius.circular(12),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              icon,
              size: 20,
              color: isPrimary ? Colors.white : const Color(0xFF6366F1),
            ),
            const SizedBox(width: 8),
            Text(
              label,
              style: TextStyle(
                fontSize: 15,
                fontWeight: FontWeight.w600,
                color: isPrimary ? Colors.white : const Color(0xFF6366F1),
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// 卡片装饰
  BoxDecoration _cardDecoration() {
    return BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withValues(alpha: 0.05),
          blurRadius: 10,
          offset: const Offset(0, 2),
        ),
      ],
    );
  }
}

3.2 二维码数据模型

/// 二维码数据类型
enum QRCodeType {
  text,  // 普通文本
  url,   // 网址链接
  wifi,  // WiFi 信息
  phone, // 电话号码
  sms,   // 短信
  email, // 邮箱
  vcard, // 电子名片
}

/// 二维码数据模型
class QRCodeData {
  /// 二维码内容
  final String data;
  
  /// 数据类型
  final QRCodeType type;
  
  /// 创建时间
  final DateTime createdAt;

  QRCodeData({
    required this.data,
    required this.type,
    DateTime? createdAt,
  }) : createdAt = createdAt ?? DateTime.now();

  /// 转换为 JSON
  Map<String, dynamic> toJson() => {
    'data': data,
    'type': type.name,
    'createdAt': createdAt.toIso8601String(),
  };

  /// 从 JSON 创建
  factory QRCodeData.fromJson(Map<String, dynamic> json) => QRCodeData(
    data: json['data'] as String,
    type: QRCodeType.values.firstWhere(
      (e) => e.name == json['type'],
      orElse: () => QRCodeType.text,
    ),
    createdAt: DateTime.parse(json['createdAt'] as String),
  );
}

/// 二维码辅助工具
class QRCodeHelper {
  /// 生成 WiFi 二维码
  /// 
  /// [ssid] WiFi 名称
  /// [password] WiFi 密码
  /// [encryption] 加密类型 (WPA, WEP, nopass)
  static String generateWiFi({
    required String ssid,
    String? password,
    String encryption = 'WPA',
  }) {
    if (password == null || password.isEmpty) {
      return 'WIFI:T:nopass;S:$ssid;;';
    }
    return 'WIFI:T:$encryption;S:$ssid;P:$password;;';
  }

  /// 生成电话二维码
  static String generatePhone(String phoneNumber) {
    return 'TEL:$phoneNumber';
  }

  /// 生成短信二维码
  static String generateSMS(String phoneNumber, {String? message}) {
    if (message == null || message.isEmpty) {
      return 'SMSTO:$phoneNumber';
    }
    return 'SMSTO:$phoneNumber:$message';
  }

  /// 生成邮箱二维码
  static String generateEmail(String email, {String? subject, String? body}) {
    String result = 'mailto:$email';
    final params = <String>[];
    if (subject != null) params.add('subject=${Uri.encodeComponent(subject)}');
    if (body != null) params.add('body=${Uri.encodeComponent(body)}');
    if (params.isNotEmpty) {
      result += '?${params.join('&')}';
    }
    return result;
  }

  /// 生成 vCard 名片
  static String generateVCard({
    String? name,
    String? phone,
    String? email,
    String? org,
  }) {
    final buffer = StringBuffer('BEGIN:VCARD\n');
    buffer.writeln('VERSION:3.0');
    if (name != null) buffer.writeln('FN:$name');
    if (phone != null) buffer.writeln('TEL:$phone');
    if (email != null) buffer.writeln('EMAIL:$email');
    if (org != null) buffer.writeln('ORG:$org');
    buffer.write('END:VCARD');
    return buffer.toString();
  }
}

四,开发踩坑与挫折 😤

4.1 踩坑一:二维码太复杂无法识别

问题描述
生成的二维码用手机扫不出来。

原因分析
数据内容太多,二维码太密集。

解决方案

  • 使用短链接
  • 减少数据量
  • 使用更高的 QR 版本
// 指定更高的版本
QrImageView(
  data: _currentData,
  version: QrVersions.auto,  // 自动选择最合适的版本
  // 或者手动指定
  // version: 5,  // 版本 1-40
)

4.2 踩坑二:白色边框不够

问题描述
二维码边缘被截断。

解决方案
确保二维码周围有足够的空白区域:

QrImageView(
  data: _currentData,
  size: 200,
  // QrImageView 默认有 4 个模块的边距
  // 如果还是不够,可以在外面加 Container 设置 padding
)

4.3 踩坑三:深色背景上用深色二维码

问题描述
深色背景上生成的深色二维码扫不出来。

解决方案
确保二维码颜色和背景有足够对比度:

Container(
  color: Colors.black,  // 深色背景
  child: QrImageView(
    data: _currentData,
    backgroundColor: Colors.black,
    eyeStyle: QrEyeStyle(color: Colors.white),  // 白色眼睛
    dataModuleStyle: QrDataModuleStyle(color: Colors.white),  // 白色数据点
  ),
)

五、最终实现效果 📸

(此处附鸿蒙设备上成功运行的截图)

在这里插入图片描述

六、个人学习总结 📝

通过二维码功能的学习,我收获了很多:

  1. ✅ 学会了 qr_flutter 的各种配置
  2. ✅ 学会了自定义二维码样式
  3. ✅ 学会了分享二维码图片

二维码虽小,但功能很强大!这个小功能真的很有用!


💡 提示:完整代码已开源至 AtomGit,欢迎 Star 和 Fork!

🔗 仓库地址:https://atomgit.com

作者:上海某高校大一学生,Flutter 爱好者
发布时间:2026年4月

Logo

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

更多推荐