Flutter for OpenHarmony 二维码扫描App实战 - 短信二维码生成实现
短信二维码生成实现 短信二维码通过sms协议实现,格式为sms:电话?body=内容。本文介绍了Flutter实现的短信二维码生成页面,包含以下功能: 协议处理:使用sms协议格式,自动预填收件人和内容 输入验证:验证手机号格式和内容非空 字数统计:显示160字限制的实时计数 UI实现:包含收件人号码和短信内容输入框,底部生成按钮 实现要点包括: 使用TextEditingController管理
短信二维码可以将短信信息编码成二维码,扫描后可以直接打开短信应用并预填收件人和内容。这在营销活动、投票、订阅服务等场景下很有用。这篇文章介绍短信二维码生成页面的实现,包括 sms 协议、内容编码、字数限制等功能。
sms 协议介绍
短信二维码使用 sms 协议,格式如下:
sms:电话号码?body=短信内容
例如:sms:13800138000?body=Hello%20World
各部分说明:
sms::协议前缀,表示这是一个短信链接
电话号码:收件人的手机号码
body:短信内容,需要进行 URL 编码
扫描后,手机会打开短信应用,自动填入收件人和内容,用户只需点击发送即可。
SmsQrView 的基础结构
先来看文件的导入和类定义:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../data/models/qr_record.dart';
import 'generate_controller.dart';
class SmsQrView extends StatefulWidget {
const SmsQrView({super.key});
State<SmsQrView> createState() => _SmsQrViewState();
}
导入了必要的包。SmsQrView 使用 StatefulWidget,因为需要管理多个输入框的状态。
状态类的定义
状态类中定义了两个输入控制器:
class _SmsQrViewState extends State<SmsQrView> {
final _phoneController = TextEditingController();
final _messageController = TextEditingController();
final _controller = Get.find<GenerateController>();
void dispose() {
_phoneController.dispose();
_messageController.dispose();
super.dispose();
}
_phoneController 控制收件人号码输入框,_messageController 控制短信内容输入框。dispose 中释放两个控制器。
Scaffold 和 AppBar
build 方法返回页面结构:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('短信二维码')),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Scaffold 提供基础页面结构,AppBar 标题为"短信二维码"。body 使用 SingleChildScrollView 包裹,支持内容滚动。
收件人号码输入
第一个输入项是收件人号码:
Text('收件人号码', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
SizedBox(height: 8.h),
TextField(
controller: _phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: '输入手机号码',
prefixIcon: const Icon(Icons.phone),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
),
),
SizedBox(height: 16.h),
keyboardType 设为 TextInputType.phone,显示数字键盘。prefixIcon 使用电话图标。
短信内容输入
第二个输入项是短信内容:
Text('短信内容', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
SizedBox(height: 8.h),
TextField(
controller: _messageController,
maxLines: 4,
maxLength: 160,
decoration: InputDecoration(
hintText: '输入短信内容',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
),
),
],
),
),
maxLines 设为 4,让输入框显示 4 行高度。maxLength 设为 160,这是单条短信的标准长度限制。
TextField 会自动显示字数计数器,用户可以看到还能输入多少字符。
底部生成按钮
页面底部是生成按钮:
bottomNavigationBar: SafeArea(
child: Padding(
padding: EdgeInsets.all(16.w),
child: ElevatedButton(
onPressed: () => _controller.generateQr(
'sms:${_phoneController.text}?body=${Uri.encodeComponent(_messageController.text)}',
QrType.sms,
),
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 48.h),
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
child: const Text('生成二维码'),
),
),
),
);
}
}
点击按钮时,将号码和内容拼接成 sms 协议格式。Uri.encodeComponent 对短信内容进行 URL 编码,确保特殊字符能正确传递。
输入验证
在生成前验证输入:
void _handleGenerate() {
final phone = _phoneController.text.trim();
final message = _messageController.text;
if (phone.isEmpty) {
Get.snackbar('提示', '请输入收件人号码', snackPosition: SnackPosition.BOTTOM);
return;
}
// 验证手机号格式
if (!_isValidPhone(phone)) {
Get.snackbar('提示', '请输入有效的手机号码', snackPosition: SnackPosition.BOTTOM);
return;
}
if (message.isEmpty) {
Get.snackbar('提示', '请输入短信内容', snackPosition: SnackPosition.BOTTOM);
return;
}
final sms = 'sms:$phone?body=${Uri.encodeComponent(message)}';
_controller.generateQr(sms, QrType.sms);
}
bool _isValidPhone(String phone) {
final digits = phone.replaceAll(RegExp(r'[^\d]'), '');
return digits.length == 11 && digits.startsWith('1');
}
验证号码不为空且是有效的手机号,短信内容不为空。
字数统计
自定义字数统计显示:
Widget _buildMessageField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('短信内容', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
Text(
'${_messageController.text.length}/160',
style: TextStyle(
fontSize: 12.sp,
color: _messageController.text.length > 140 ? Colors.orange : Colors.grey,
),
),
],
),
SizedBox(height: 8.h),
TextField(
controller: _messageController,
maxLines: 4,
maxLength: 160,
decoration: InputDecoration(
hintText: '输入短信内容',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
counterText: '', // 隐藏默认计数器
),
onChanged: (_) => setState(() {}),
),
SizedBox(height: 4.h),
Text(
'超过70个汉字将分成多条短信发送',
style: TextStyle(fontSize: 11.sp, color: Colors.grey),
),
],
);
}
字数统计显示在标题行,接近上限时变成橙色。底部提示用户关于短信分条的规则。
短信模板
提供一些常用的短信模板:
Widget _buildTemplates() {
final templates = [
{'name': '验证码', 'content': '您的验证码是:____,5分钟内有效。'},
{'name': '会议通知', 'content': '会议通知:____,时间:____,地点:____'},
{'name': '订单确认', 'content': '您的订单已确认,订单号:____'},
{'name': '活动邀请', 'content': '诚邀您参加____活动,时间:____'},
{'name': '投票', 'content': '回复数字参与投票:1.____ 2.____'},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Text('快速模板', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
SizedBox(height: 8.h),
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: templates.map((template) => ActionChip(
label: Text(template['name']!),
onPressed: () => _messageController.text = template['content']!,
)).toList(),
),
],
);
}
点击模板按钮可以快速填充短信内容,用户只需修改占位符即可。
从通讯录选择
提供从通讯录选择联系人的功能:
import 'package:contacts_service/contacts_service.dart';
Future<void> _pickFromContacts() async {
final contact = await ContactsService.openDeviceContactPicker();
if (contact != null && contact.phones != null && contact.phones!.isNotEmpty) {
final phone = contact.phones!.first.value;
if (phone != null) {
final cleanPhone = phone.replaceAll(RegExp(r'[^\d]'), '');
_phoneController.text = cleanPhone;
Get.snackbar(
'已选择',
contact.displayName ?? cleanPhone,
snackPosition: SnackPosition.BOTTOM,
);
}
}
}
使用 contacts_service 插件打开系统通讯录选择器。
短信预览
显示短信预览:
Widget _buildPreview() {
if (_phoneController.text.isEmpty && _messageController.text.isEmpty) {
return const SizedBox.shrink();
}
return Card(
margin: EdgeInsets.only(top: 16.h),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.sms, color: Colors.green),
SizedBox(width: 8.w),
Text('短信预览', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
],
),
Divider(height: 24.h),
Row(
children: [
Text('收件人: ', style: TextStyle(fontSize: 12.sp, color: Colors.grey)),
Text(
_phoneController.text.isEmpty ? '未填写' : _phoneController.text,
style: TextStyle(fontSize: 13.sp),
),
],
),
SizedBox(height: 8.h),
Container(
width: double.infinity,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.green[50],
borderRadius: BorderRadius.circular(8.r),
),
child: Text(
_messageController.text.isEmpty ? '短信内容' : _messageController.text,
style: TextStyle(fontSize: 13.sp),
),
),
],
),
),
);
}
预览区域模拟短信气泡样式,让用户看到最终效果。
测试发送
提供测试发送功能:
import 'package:url_launcher/url_launcher.dart';
Future<void> _testSend() async {
final phone = _phoneController.text.trim();
final message = _messageController.text;
if (phone.isEmpty) {
Get.snackbar('提示', '请先输入收件人号码', snackPosition: SnackPosition.BOTTOM);
return;
}
final sms = 'sms:$phone?body=${Uri.encodeComponent(message)}';
final uri = Uri.parse(sms);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
Get.snackbar('错误', '无法打开短信应用', snackPosition: SnackPosition.BOTTOM);
}
}
使用 url_launcher 插件打开 sms 链接,会启动系统短信应用。
多收件人支持
支持添加多个收件人:
class _SmsQrViewState extends State<SmsQrView> {
final List<TextEditingController> _phoneControllers = [TextEditingController()];
void _addRecipient() {
setState(() {
_phoneControllers.add(TextEditingController());
});
}
void _removeRecipient(int index) {
if (_phoneControllers.length > 1) {
setState(() {
_phoneControllers[index].dispose();
_phoneControllers.removeAt(index);
});
}
}
String _generateSms() {
final phones = _phoneControllers
.map((c) => c.text.trim())
.where((p) => p.isNotEmpty)
.join(',');
return 'sms:$phones?body=${Uri.encodeComponent(_messageController.text)}';
}
}
多个收件人用逗号分隔。
特殊字符处理
处理短信内容中的特殊字符:
String _encodeMessage(String message) {
// URL 编码
var encoded = Uri.encodeComponent(message);
// 某些设备需要特殊处理换行符
encoded = encoded.replaceAll('%0A', '%0D%0A');
return encoded;
}
不同设备对换行符的处理可能不同,需要特殊处理。
短信费用提示
显示短信费用提示:
Widget _buildCostHint() {
final length = _messageController.text.length;
int smsCount;
// 计算短信条数
if (length <= 70) {
smsCount = 1;
} else {
smsCount = (length / 67).ceil(); // 长短信每条67字
}
return Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
children: [
Icon(Icons.info_outline, size: 16.sp, color: Colors.grey),
SizedBox(width: 4.w),
Text(
'预计发送 $smsCount 条短信',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
);
}
根据内容长度计算短信条数,提醒用户费用。
表情符号支持
提供常用表情符号:
Widget _buildEmojis() {
final emojis = ['😊', '👍', '❤️', '🎉', '✅', '⭐', '📱', '💬'];
return Wrap(
spacing: 8.w,
children: emojis.map((emoji) => GestureDetector(
onTap: () {
final text = _messageController.text;
final selection = _messageController.selection;
final newText = text.replaceRange(
selection.start,
selection.end,
emoji,
);
_messageController.text = newText;
_messageController.selection = TextSelection.collapsed(
offset: selection.start + emoji.length,
);
},
child: Container(
padding: EdgeInsets.all(8.w),
child: Text(emoji, style: TextStyle(fontSize: 20.sp)),
),
)).toList(),
);
}
点击表情符号可以插入到短信内容中。
历史记录
显示最近发送的短信:
Widget _buildRecentSms() {
return Obx(() {
final recentSms = _controller.recentRecords
.where((r) => r.type == QrType.sms)
.take(3)
.toList();
if (recentSms.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Text('最近使用', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
SizedBox(height: 8.h),
...recentSms.map((record) {
final parts = record.content.replaceFirst('sms:', '').split('?body=');
final phone = parts[0];
final body = parts.length > 1 ? Uri.decodeComponent(parts[1]) : '';
return ListTile(
dense: true,
leading: const Icon(Icons.history),
title: Text(phone),
subtitle: Text(
body,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
onTap: () {
_phoneController.text = phone;
_messageController.text = body;
},
);
}),
],
);
});
}
从历史记录中提取号码和内容,点击可以快速填充。
小结
短信二维码生成页面使用 sms 协议编码短信信息。页面包含收件人号码和短信内容两个输入字段,支持多收件人、模板、表情符号等功能。
URL 编码确保特殊字符能正确传递。字数统计和费用提示帮助用户了解短信长度限制。预览和测试发送功能让用户在生成二维码前确认内容。
短信二维码在营销活动、投票、订阅服务等场景下很有用,扫描后可以直接打开短信应用发送预设内容。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)