在这里插入图片描述

二维码在现代应用中无处不在。今天我们来实现一个二维码生成工具,让文本、URL等信息快速转换为二维码。

功能规划

二维码生成工具需要:

文本转二维码:输入任意文本生成二维码。

尺寸调整:自定义二维码大小。

颜色定制:修改二维码的前景色和背景色。

容错级别:选择不同的容错等级。

保存分享:保存二维码图片或分享。

完整代码实现

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';

class QrCodeGeneratorPage extends StatefulWidget {
  const QrCodeGeneratorPage({Key? key}) : super(key: key);

  
  State<QrCodeGeneratorPage> createState() => _QrCodeGeneratorPageState();
}

class _QrCodeGeneratorPageState extends State<QrCodeGeneratorPage> {
  final TextEditingController _textController = TextEditingController();
  
  double _qrSize = 200;
  Color _foregroundColor = Colors.black;
  Color _backgroundColor = Colors.white;
  int _errorCorrectionLevel = 0; // 0: L, 1: M, 2: Q, 3: H

  
  void initState() {
    super.initState();
    _textController.text = 'https://openharmonycrossplatform.csdn.net';
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('二维码生成'),
        actions: [
          IconButton(
            icon: const Icon(Icons.download),
            onPressed: _saveQrCode,
            tooltip: '保存二维码',
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            // 输入区域
            _buildInputSection(),
            SizedBox(height: 24.h),
            
            // 二维码预览
            _buildQrCodePreview(),
            SizedBox(height: 24.h),
            
            // 自定义选项
            _buildCustomOptions(),
            SizedBox(height: 24.h),
            
            // 快捷模板
            _buildQuickTemplates(),
          ],
        ),
      ),
    );
  }

  Widget _buildInputSection() {
    return Card(
      elevation: 2,
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.edit, size: 20.sp, color: Colors.blue),
                SizedBox(width: 8.w),
                Text(
                  '输入内容',
                  style: TextStyle(
                    fontSize: 16.sp,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            SizedBox(height: 12.h),
            TextField(
              controller: _textController,
              maxLines: 3,
              style: TextStyle(fontSize: 14.sp),
              decoration: InputDecoration(
                hintText: '输入文本、URL、电话号码等...',
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(8.r),
                ),
                contentPadding: EdgeInsets.all(12.w),
              ),
              onChanged: (_) => setState(() {}),
            ),
            SizedBox(height: 8.h),
            Text(
              '字符数: ${_textController.text.length}',
              style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildQrCodePreview() {
    return Card(
      elevation: 4,
      child: Container(
        padding: EdgeInsets.all(24.w),
        child: Column(
          children: [
            Text(
              '二维码预览',
              style: TextStyle(
                fontSize: 16.sp,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 16.h),
            Container(
              width: _qrSize,
              height: _qrSize,
              decoration: BoxDecoration(
                color: _backgroundColor,
                borderRadius: BorderRadius.circular(8.r),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: _textController.text.isEmpty
                  ? Center(
                      child: Text(
                        '输入内容生成二维码',
                        style: TextStyle(
                          fontSize: 12.sp,
                          color: Colors.grey[600],
                        ),
                      ),
                    )
                  : _buildQrCodeWidget(),
            ),
            SizedBox(height: 16.h),
            Text(
              '尺寸: ${_qrSize.toInt()}x${_qrSize.toInt()}',
              style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildQrCodeWidget() {
    // 简化的二维码显示
    // 实际项目中使用 qr_flutter 包
    return CustomPaint(
      painter: QrCodePainter(
        data: _textController.text,
        foregroundColor: _foregroundColor,
      ),
      size: Size(_qrSize, _qrSize),
    );
  }

  Widget _buildCustomOptions() {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '自定义选项',
              style: TextStyle(
                fontSize: 16.sp,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 16.h),
            
            // 尺寸调整
            Text('尺寸', style: TextStyle(fontSize: 14.sp)),
            Slider(
              value: _qrSize,
              min: 100,
              max: 300,
              divisions: 20,
              label: _qrSize.toInt().toString(),
              onChanged: (value) {
                setState(() => _qrSize = value);
              },
            ),
            SizedBox(height: 16.h),
            
            // 颜色选择
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('前景色', style: TextStyle(fontSize: 14.sp)),
                      SizedBox(height: 8.h),
                      _buildColorPicker(_foregroundColor, (color) {
                        setState(() => _foregroundColor = color);
                      }),
                    ],
                  ),
                ),
                SizedBox(width: 16.w),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('背景色', style: TextStyle(fontSize: 14.sp)),
                      SizedBox(height: 8.h),
                      _buildColorPicker(_backgroundColor, (color) {
                        setState(() => _backgroundColor = color);
                      }),
                    ],
                  ),
                ),
              ],
            ),
            SizedBox(height: 16.h),
            
            // 容错级别
            Text('容错级别', style: TextStyle(fontSize: 14.sp)),
            SizedBox(height: 8.h),
            Wrap(
              spacing: 8.w,
              children: [
                _buildErrorLevelChip('L (7%)', 0),
                _buildErrorLevelChip('M (15%)', 1),
                _buildErrorLevelChip('Q (25%)', 2),
                _buildErrorLevelChip('H (30%)', 3),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildColorPicker(Color currentColor, Function(Color) onColorChanged) {
    final colors = [
      Colors.black,
      Colors.white,
      Colors.red,
      Colors.blue,
      Colors.green,
      Colors.orange,
      Colors.purple,
      Colors.pink,
    ];

    return Wrap(
      spacing: 8.w,
      children: colors.map((color) {
        final isSelected = color.value == currentColor.value;
        return GestureDetector(
          onTap: () => onColorChanged(color),
          child: Container(
            width: 32.w,
            height: 32.w,
            decoration: BoxDecoration(
              color: color,
              shape: BoxShape.circle,
              border: Border.all(
                color: isSelected ? Colors.blue : Colors.grey[300]!,
                width: isSelected ? 3 : 1,
              ),
            ),
          ),
        );
      }).toList(),
    );
  }

  Widget _buildErrorLevelChip(String label, int level) {
    final isSelected = _errorCorrectionLevel == level;
    return ChoiceChip(
      label: Text(label),
      selected: isSelected,
      onSelected: (selected) {
        if (selected) {
          setState(() => _errorCorrectionLevel = level);
        }
      },
    );
  }

  Widget _buildQuickTemplates() {
    final templates = [
      {'icon': Icons.link, 'title': 'URL', 'prefix': 'https://'},
      {'icon': Icons.email, 'title': '邮箱', 'prefix': 'mailto:'},
      {'icon': Icons.phone, 'title': '电话', 'prefix': 'tel:'},
      {'icon': Icons.message, 'title': '短信', 'prefix': 'sms:'},
      {'icon': Icons.wifi, 'title': 'WiFi', 'prefix': 'WIFI:T:WPA;S:;P:;;'},
      {'icon': Icons.location_on, 'title': '位置', 'prefix': 'geo:'},
    ];

    return Card(
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '快捷模板',
              style: TextStyle(
                fontSize: 16.sp,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 12.h),
            GridView.builder(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                crossAxisSpacing: 8.w,
                mainAxisSpacing: 8.h,
                childAspectRatio: 1.5,
              ),
              itemCount: templates.length,
              itemBuilder: (context, index) {
                final template = templates[index];
                return InkWell(
                  onTap: () {
                    _textController.text = template['prefix'] as String;
                    setState(() {});
                  },
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.blue[50],
                      borderRadius: BorderRadius.circular(8.r),
                      border: Border.all(color: Colors.blue[200]!),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(
                          template['icon'] as IconData,
                          color: Colors.blue,
                          size: 24.sp,
                        ),
                        SizedBox(height: 4.h),
                        Text(
                          template['title'] as String,
                          style: TextStyle(
                            fontSize: 12.sp,
                            color: Colors.blue[900],
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  void _saveQrCode() {
    if (_textController.text.isEmpty) {
      Get.snackbar('提示', '请先输入内容',
          snackPosition: SnackPosition.BOTTOM);
      return;
    }

    // 实际项目中这里会保存二维码图片
    Get.snackbar('提示', '保存功能需要文件系统权限',
        snackPosition: SnackPosition.BOTTOM);
  }
}

// 简化的二维码绘制器
class QrCodePainter extends CustomPainter {
  final String data;
  final Color foregroundColor;

  QrCodePainter({
    required this.data,
    required this.foregroundColor,
  });

  
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = foregroundColor
      ..style = PaintingStyle.fill;

    // 简化的二维码模拟
    // 实际项目中使用 qr_flutter 包生成真实的二维码
    final cellSize = size.width / 25;
    
    // 绘制定位标记
    _drawPositionMarker(canvas, paint, 0, 0, cellSize);
    _drawPositionMarker(canvas, paint, size.width - 7 * cellSize, 0, cellSize);
    _drawPositionMarker(canvas, paint, 0, size.height - 7 * cellSize, cellSize);
    
    // 绘制随机模式(模拟数据)
    for (int i = 0; i < 25; i++) {
      for (int j = 0; j < 25; j++) {
        if ((i + j + data.length) % 3 == 0) {
          canvas.drawRect(
            Rect.fromLTWH(i * cellSize, j * cellSize, cellSize * 0.9, cellSize * 0.9),
            paint,
          );
        }
      }
    }
  }

  void _drawPositionMarker(Canvas canvas, Paint paint, double x, double y, double cellSize) {
    // 外框
    canvas.drawRect(
      Rect.fromLTWH(x, y, cellSize * 7, cellSize * 7),
      paint,
    );
    
    // 内部空白
    canvas.drawRect(
      Rect.fromLTWH(x + cellSize, y + cellSize, cellSize * 5, cellSize * 5),
      Paint()..color = Colors.white,
    );
    
    // 中心点
    canvas.drawRect(
      Rect.fromLTWH(x + cellSize * 2, y + cellSize * 2, cellSize * 3, cellSize * 3),
      paint,
    );
  }

  
  bool shouldRepaint(QrCodePainter oldDelegate) {
    return oldDelegate.data != data || oldDelegate.foregroundColor != foregroundColor;
  }
}

二维码生成原理

二维码是一种二维条码,可以存储大量信息。实际项目中使用qr_flutter包:

import 'package:qr_flutter/qr_flutter.dart';

QrImageView(
  data: _textController.text,
  version: QrVersions.auto,
  size: _qrSize,
  foregroundColor: _foregroundColor,
  backgroundColor: _backgroundColor,
)

我们的示例代码用CustomPainter模拟了二维码的外观。

尺寸调整

使用Slider控制二维码大小:

Slider(
  value: _qrSize,
  min: 100,
  max: 300,
  divisions: 20,
  label: _qrSize.toInt().toString(),
  onChanged: (value) {
    setState(() => _qrSize = value);
  },
)

范围从100到300像素,分成20个刻度。

颜色定制

提供前景色和背景色选择:

Widget _buildColorPicker(Color currentColor, Function(Color) onColorChanged) {
  final colors = [Colors.black, Colors.white, Colors.red, ...];
  
  return Wrap(
    children: colors.map((color) {
      return GestureDetector(
        onTap: () => onColorChanged(color),
        child: Container(
          decoration: BoxDecoration(
            color: color,
            shape: BoxShape.circle,
          ),
        ),
      );
    }).toList(),
  );
}

用圆形色块展示可选颜色,点击即可切换。

容错级别

二维码有四个容错级别:

L级:约7%的容错能力

M级:约15%的容错能力

Q级:约25%的容错能力

H级:约30%的容错能力

ChoiceChip(
  label: Text('L (7%)'),
  selected: _errorCorrectionLevel == 0,
  onSelected: (selected) {
    setState(() => _errorCorrectionLevel = 0);
  },
)

容错级别越高,二维码越复杂,但损坏后仍能识别。

快捷模板

提供常用的二维码类型模板:

final templates = [
  {'icon': Icons.link, 'title': 'URL', 'prefix': 'https://'},
  {'icon': Icons.email, 'title': '邮箱', 'prefix': 'mailto:'},
  {'icon': Icons.phone, 'title': '电话', 'prefix': 'tel:'},
  // ...
];

点击模板自动填充对应的前缀,用户只需补充具体内容。

WiFi二维码

WiFi二维码的格式比较特殊:

WIFI:T:WPA;S:网络名称;P:密码;;

T是加密类型(WPA/WEP/nopass),S是SSID,P是密码。

保存功能

保存二维码为图片:

void _saveQrCode() {
  // 使用 RepaintBoundary 捕获Widget为图片
  // 然后保存到相册
}

实际实现需要文件系统权限和图片处理库。

功能扩展建议

Logo嵌入:在二维码中心添加Logo。

样式定制:圆角、渐变等特殊样式。

批量生成:一次生成多个二维码。

扫码功能:集成扫码识别二维码内容。

vCard支持:生成电子名片二维码。

实战经验

做二维码工具时,最重要的是提供足够的定制选项。

一开始我只支持黑白二维码,但用户反馈说想要彩色的。加入颜色选择后,工具的实用性大大提升。

还有一个细节:快捷模板。很多人不知道二维码可以存储电话、邮箱等特殊格式。提供模板后,用户可以快速生成各种类型的二维码。

小结

二维码生成器通过简洁的界面和丰富的定制选项,让二维码生成变得简单。支持多种内容类型,满足不同场景的需求。

记住:工具要考虑用户的实际需求,提供常用的功能和模板。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐