二维码预览页面是生成流程的最后一步,用户在这里可以看到生成的二维码,并进行保存、分享等操作。这篇文章介绍二维码预览页面的实现,包括二维码显示、内容信息展示、保存分享功能等。

二维码预览的设计思路

预览页面需要展示以下内容:

二维码图片:生成的二维码图片,是页面的核心内容

内容信息:二维码编码的原始内容,方便用户确认

类型标签:二维码的类型,如网址、文本、WiFi 等

操作按钮:保存到相册、分享、重新生成等操作

页面布局采用上下结构,上方是二维码和信息,下方是操作按钮。

QrPreviewView 的基础结构

先来看文件的导入和类定义:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../data/models/qr_record.dart';
import '../../routes/app_pages.dart';

class QrPreviewView extends StatelessWidget {
  const QrPreviewView({super.key});

导入了必要的包。QrPreviewView 使用 StatelessWidget,因为页面状态主要来自传入的参数,不需要本地状态管理。

获取传入的记录

build 方法开始时获取传入的记录:

  
  Widget build(BuildContext context) {
    final record = Get.arguments as QrRecord;

    return Scaffold(
      appBar: AppBar(
        title: const Text('二维码预览'),
        actions: [
          IconButton(
            icon: const Icon(Icons.palette),
            onPressed: () => Get.toNamed(Routes.QR_STYLE),
            tooltip: '样式设置',
          ),
        ],
      ),

Get.arguments 获取路由传递的参数,这里是 QrRecord 对象。AppBar 的 actions 中有一个样式设置按钮,点击可以跳转到样式设置页面。

页面主体布局

body 使用 SingleChildScrollView 包裹:

      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [

SingleChildScrollView 让页面内容可以滚动,适应不同屏幕尺寸。padding 设置四周 16.w 的内边距。

二维码显示区域

二维码显示区域使用 Card 包裹:

            Card(
              child: Padding(
                padding: EdgeInsets.all(24.w),
                child: Column(
                  children: [
                    Container(
                      width: 200.w,
                      height: 200.w,
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(8.r),
                        border: Border.all(color: Colors.grey[300]!),
                      ),
                      child: Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Icon(Icons.qr_code_2, size: 120.sp, color: Colors.black87),
                            SizedBox(height: 8.h),
                            Text(
                              record.typeLabel,
                              style: TextStyle(fontSize: 12.sp, color: Colors.grey),
                            ),
                          ],
                        ),
                      ),
                    ),

Card 提供卡片效果,内部 padding 为 24.w。Container 设置固定的宽高 200.w,白色背景,圆角边框。

内部显示二维码图标和类型标签。在实际项目中,这里应该使用 qr_flutter 等库生成真实的二维码图片。

生成成功提示

二维码下方显示生成成功的提示:

                    SizedBox(height: 16.h),
                    Text(
                      '二维码已生成',
                      style: TextStyle(
                        fontSize: 16.sp,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ],
                ),
              ),
            ),
            SizedBox(height: 16.h),

简单的文字提示,让用户知道二维码已经生成成功。

内容信息卡片

显示二维码编码的内容:

            Card(
              child: Padding(
                padding: EdgeInsets.all(16.w),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.info_outline, size: 20.sp),
                        SizedBox(width: 8.w),
                        Text(
                          '二维码内容',
                          style: TextStyle(
                            fontSize: 14.sp,
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 8.h),
                    Text(
                      record.content,
                      style: TextStyle(fontSize: 12.sp, color: Colors.grey[700]),
                      maxLines: 5,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ],
                ),
              ),
            ),
            SizedBox(height: 24.h),

Row 显示图标和标题。Text 显示内容,限制最多 5 行,超出部分用省略号截断。

操作按钮区域

底部是保存和分享按钮:

            Row(
              children: [
                Expanded(
                  child: OutlinedButton.icon(
                    onPressed: () {
                      Get.snackbar('成功', '已保存到相册',
                          snackPosition: SnackPosition.BOTTOM);
                    },
                    icon: const Icon(Icons.save_alt),
                    label: const Text('保存'),
                    style: OutlinedButton.styleFrom(
                      minimumSize: Size(0, 48.h),
                    ),
                  ),
                ),
                SizedBox(width: 12.w),
                Expanded(
                  child: OutlinedButton.icon(
                    onPressed: () {
                      Get.snackbar('提示', '分享功能',
                          snackPosition: SnackPosition.BOTTOM);
                    },
                    icon: const Icon(Icons.share),
                    label: const Text('分享'),
                    style: OutlinedButton.styleFrom(
                      minimumSize: Size(0, 48.h),
                    ),
                  ),
                ),
              ],
            ),

Row 让两个按钮水平排列,Expanded 让它们平分宽度。OutlinedButton.icon 是带图标的描边按钮。

生成新的按钮

最下方是生成新的按钮:

            SizedBox(height: 12.h),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton.icon(
                onPressed: () => Get.back(),
                icon: const Icon(Icons.add),
                label: const Text('生成新的'),
                style: ElevatedButton.styleFrom(
                  minimumSize: Size(0, 48.h),
                  backgroundColor: Theme.of(context).primaryColor,
                  foregroundColor: Colors.white,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ElevatedButton.icon 是带图标的凸起按钮,点击后返回上一页,用户可以生成新的二维码。

真实二维码生成

使用 qr_flutter 库生成真实的二维码:

import 'package:qr_flutter/qr_flutter.dart';

Widget _buildQrImage(QrRecord record) {
  return QrImageView(
    data: record.content,
    version: QrVersions.auto,
    size: 200.w,
    backgroundColor: Colors.white,
    errorCorrectionLevel: QrErrorCorrectLevel.M,
    embeddedImage: record.logoPath != null 
        ? FileImage(File(record.logoPath!)) 
        : null,
    embeddedImageStyle: QrEmbeddedImageStyle(
      size: Size(40.w, 40.w),
    ),
  );
}

QrImageView 是 qr_flutter 提供的二维码组件。data 是要编码的内容,version 设为 auto 自动选择版本,size 是二维码大小。

errorCorrectionLevel 设置纠错级别,M 级别可以恢复约 15% 的数据丢失。embeddedImage 可以在二维码中心嵌入 Logo。

保存到相册

实现保存到相册的功能:

import 'dart:ui' as ui;
import 'package:image_gallery_saver/image_gallery_saver.dart';

Future<void> _saveToGallery(QrRecord record) async {
  // 显示加载对话框
  Get.dialog(
    const Center(child: CircularProgressIndicator()),
    barrierDismissible: false,
  );
  
  try {
    // 生成二维码图片
    final qrPainter = QrPainter(
      data: record.content,
      version: QrVersions.auto,
      errorCorrectionLevel: QrErrorCorrectLevel.M,
    );
    
    final picData = await qrPainter.toImageData(300);
    if (picData == null) {
      Get.back();
      Get.snackbar('错误', '生成图片失败', snackPosition: SnackPosition.BOTTOM);
      return;
    }
    
    // 保存到相册
    final result = await ImageGallerySaver.saveImage(
      picData.buffer.asUint8List(),
      quality: 100,
      name: 'qr_${DateTime.now().millisecondsSinceEpoch}',
    );
    
    Get.back();
    
    if (result['isSuccess']) {
      Get.snackbar('成功', '已保存到相册', snackPosition: SnackPosition.BOTTOM);
    } else {
      Get.snackbar('错误', '保存失败', snackPosition: SnackPosition.BOTTOM);
    }
  } catch (e) {
    Get.back();
    Get.snackbar('错误', '保存失败: $e', snackPosition: SnackPosition.BOTTOM);
  }
}

使用 QrPainter 生成二维码图片数据,然后使用 image_gallery_saver 插件保存到相册。

分享功能

实现分享功能:

import 'package:share_plus/share_plus.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

Future<void> _shareQr(QrRecord record) async {
  Get.dialog(
    const Center(child: CircularProgressIndicator()),
    barrierDismissible: false,
  );
  
  try {
    // 生成二维码图片
    final qrPainter = QrPainter(
      data: record.content,
      version: QrVersions.auto,
      errorCorrectionLevel: QrErrorCorrectLevel.M,
    );
    
    final picData = await qrPainter.toImageData(300);
    if (picData == null) {
      Get.back();
      Get.snackbar('错误', '生成图片失败', snackPosition: SnackPosition.BOTTOM);
      return;
    }
    
    // 保存到临时文件
    final directory = await getTemporaryDirectory();
    final file = File('${directory.path}/qr_share.png');
    await file.writeAsBytes(picData.buffer.asUint8List());
    
    Get.back();
    
    // 分享
    await Share.shareXFiles(
      [XFile(file.path)],
      text: '扫描二维码查看: ${record.typeLabel}',
    );
  } catch (e) {
    Get.back();
    Get.snackbar('错误', '分享失败: $e', snackPosition: SnackPosition.BOTTOM);
  }
}

先将二维码保存到临时文件,然后使用 share_plus 插件分享。

复制内容

提供复制二维码内容的功能:

import 'package:flutter/services.dart';

void _copyContent(String content) {
  Clipboard.setData(ClipboardData(text: content));
  Get.snackbar('成功', '已复制到剪贴板', snackPosition: SnackPosition.BOTTOM);
}

使用 Clipboard 复制内容到剪贴板。

收藏功能

添加收藏按钮:

Widget _buildFavoriteButton(QrRecord record) {
  final historyService = Get.find<QrHistoryService>();
  
  return Obx(() {
    final isFavorite = historyService.favorites.any((e) => e.id == record.id);
    
    return IconButton(
      icon: Icon(
        isFavorite ? Icons.star : Icons.star_border,
        color: Colors.amber,
      ),
      onPressed: () {
        historyService.toggleFavorite(record);
        Get.snackbar(
          isFavorite ? '已取消收藏' : '已收藏',
          '',
          snackPosition: SnackPosition.BOTTOM,
        );
      },
    );
  });
}

使用 Obx 监听收藏状态变化,点击切换收藏状态。

二维码样式应用

应用用户设置的样式:

Widget _buildStyledQr(QrRecord record) {
  final controller = Get.find<GenerateController>();
  
  return Obx(() => QrImageView(
    data: record.content,
    version: QrVersions.auto,
    size: controller.qrSize.value,
    backgroundColor: controller.bgColor.value,
    eyeStyle: QrEyeStyle(
      eyeShape: QrEyeShape.square,
      color: controller.qrColor.value,
    ),
    dataModuleStyle: QrDataModuleStyle(
      dataModuleShape: QrDataModuleShape.square,
      color: controller.qrColor.value,
    ),
    embeddedImage: controller.hasLogo.value && controller.logoPath.value.isNotEmpty
        ? FileImage(File(controller.logoPath.value))
        : null,
    embeddedImageStyle: QrEmbeddedImageStyle(
      size: Size(40.w, 40.w),
    ),
  ));
}

从 GenerateController 获取样式设置,应用到二维码生成。

打印功能

提供打印二维码的功能:

import 'package:printing/printing.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

Future<void> _printQr(QrRecord record) async {
  final doc = pw.Document();
  
  // 生成二维码图片
  final qrPainter = QrPainter(
    data: record.content,
    version: QrVersions.auto,
  );
  final picData = await qrPainter.toImageData(300);
  
  if (picData == null) {
    Get.snackbar('错误', '生成图片失败', snackPosition: SnackPosition.BOTTOM);
    return;
  }
  
  final image = pw.MemoryImage(picData.buffer.asUint8List());
  
  doc.addPage(
    pw.Page(
      build: (context) => pw.Center(
        child: pw.Column(
          mainAxisAlignment: pw.MainAxisAlignment.center,
          children: [
            pw.Image(image, width: 200, height: 200),
            pw.SizedBox(height: 20),
            pw.Text(record.typeLabel),
            pw.SizedBox(height: 10),
            pw.Text(
              record.content,
              style: const pw.TextStyle(fontSize: 10),
              maxLines: 3,
            ),
          ],
        ),
      ),
    ),
  );
  
  await Printing.layoutPdf(
    onLayout: (format) async => doc.save(),
  );
}

使用 printingpdf 插件生成 PDF 并打印。

更多操作菜单

在 AppBar 添加更多操作:

PopupMenuButton<String>(
  onSelected: (value) {
    switch (value) {
      case 'copy':
        _copyContent(record.content);
        break;
      case 'print':
        _printQr(record);
        break;
      case 'edit':
        // 返回编辑页面
        Get.back(result: record);
        break;
    }
  },
  itemBuilder: (context) => [
    const PopupMenuItem(value: 'copy', child: Text('复制内容')),
    const PopupMenuItem(value: 'print', child: Text('打印')),
    const PopupMenuItem(value: 'edit', child: Text('编辑')),
  ],
),

提供复制、打印、编辑等更多操作。

二维码信息详情

显示更详细的二维码信息:

Widget _buildDetailInfo(QrRecord record) {
  return Card(
    child: Padding(
      padding: EdgeInsets.all(16.w),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildInfoRow('类型', record.typeLabel),
          _buildInfoRow('创建时间', _formatDateTime(record.createdAt)),
          _buildInfoRow('内容长度', '${record.content.length} 字符'),
          _buildInfoRow('二维码版本', _getQrVersion(record.content)),
        ],
      ),
    ),
  );
}

Widget _buildInfoRow(String label, String value) {
  return Padding(
    padding: EdgeInsets.only(bottom: 8.h),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: TextStyle(fontSize: 13.sp, color: Colors.grey)),
        Text(value, style: TextStyle(fontSize: 13.sp)),
      ],
    ),
  );
}

String _getQrVersion(String content) {
  final length = content.length;
  if (length <= 25) return '版本 1';
  if (length <= 47) return '版本 2';
  if (length <= 77) return '版本 3';
  if (length <= 114) return '版本 4';
  return '版本 5+';
}

显示类型、创建时间、内容长度、二维码版本等信息。

小结

二维码预览页面是生成流程的最后一步,需要清晰展示生成的二维码,并提供便捷的保存、分享、打印等操作。

页面使用 Card 组件组织内容,二维码显示区域突出,操作按钮易于点击。收藏功能让用户可以保存常用的二维码,样式设置让用户可以自定义二维码外观。

一个好的预览页面应该让用户一眼就能确认二维码内容是否正确,并快速完成后续操作。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐