Flutter for OpenHarmony 二维码扫描App实战 - 扫描设置实现
扫描设置功能实现 本文介绍了二维码应用中扫描设置功能的实现方案。该功能模块包含四个核心设置项:扫描提示音、震动反馈、自动打开链接和保存到历史记录。通过GetX框架实现响应式状态管理,使用SwitchListTile组件构建设置项界面,并采用Card组件增强视觉层次感。每个设置项都配有详细的说明文字,帮助用户理解功能作用。特别强调了自动打开链接选项的安全考量,建议默认关闭并增加安全检查机制。整体实现
扫描设置是二维码应用中非常重要的功能模块,它让用户可以根据自己的使用习惯来定制扫描行为。有些用户喜欢扫描成功后有声音提示,有些用户则偏好静音模式;有些用户希望扫描到网址后自动打开浏览器,有些用户则更谨慎,想先看看链接再决定是否打开。这篇文章详细介绍扫描设置页面的实现,包括各种开关选项的设计和响应式状态管理。
扫描设置的功能规划
在开始写代码之前,我先梳理了一下扫描设置需要包含哪些选项。根据用户调研和竞品分析,我确定了以下四个核心设置项:
扫描提示音:扫描成功时是否播放提示音。这个功能在嘈杂环境中特别有用,用户可以通过声音确认扫描成功,不需要一直盯着屏幕看。但在安静的场合,比如图书馆或会议室,用户可能希望关闭提示音。
震动反馈:扫描成功时是否震动。和提示音类似,震动反馈也是一种确认机制。对于听力不便的用户,震动反馈尤其重要。有些用户同时开启声音和震动,双重确认更安心。
自动打开链接:扫描到网址时是否自动打开浏览器。这个选项有一定的安全风险,如果扫描到恶意链接自动打开可能会有问题,所以默认是关闭的。但对于信任的场景,比如扫描自己生成的二维码,自动打开能省去一步操作。
保存到历史:是否自动保存扫描记录。大多数用户需要这个功能,方便以后查看扫描过的内容。但有些注重隐私的用户可能不希望留下记录,所以提供关闭选项。
文件结构和依赖导入
扫描设置页面的代码位于 lib/app/modules/settings/scan_settings_view.dart,先来看文件开头的导入部分:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../data/services/storage_service.dart';
这四行导入语句各有用途。flutter/material.dart 是 Flutter 的核心 UI 库,提供了 Scaffold、AppBar、ListView、Card、SwitchListTile 等我们需要用到的组件。get 是 GetX 框架的核心包,我们用它来获取 StorageService 实例和实现响应式状态更新。
flutter_screenutil 是屏幕适配库,让我们可以使用 .w、.h、.sp、.r 等单位,确保界面在不同尺寸的设备上都能正确显示。最后导入的 storage_service.dart 是我们自定义的存储服务,管理着所有的设置项状态。
ScanSettingsView 类的定义
接下来是页面类的定义:
class ScanSettingsView extends StatelessWidget {
const ScanSettingsView({super.key});
Widget build(BuildContext context) {
final storage = Get.find<StorageService>();
ScanSettingsView 继承自 StatelessWidget,因为页面本身不需要维护状态,所有状态都存储在 StorageService 中。使用 StatelessWidget 而不是 StatefulWidget 可以让代码更简洁,也符合 GetX 的响应式编程理念。
构造函数使用 const 修饰,表示这个组件可以作为编译时常量。super.key 是 Dart 3.0 引入的简化写法,等价于之前的 Key? key 参数传递。
在 build 方法的开头,通过 Get.find<StorageService>() 获取已注册的 StorageService 实例。这个服务在应用启动时就已经通过 Get.put 注册了,所以这里可以直接获取。Get.find 是 GetX 的依赖注入机制,它会从容器中查找指定类型的实例并返回。
Scaffold 和 AppBar 的构建
页面的整体结构使用 Scaffold 组件:
return Scaffold(
appBar: AppBar(title: const Text('扫描设置')),
body: ListView(
padding: EdgeInsets.all(16.w),
children: [
Card(
child: Column(
children: [
Scaffold 是 Material Design 的基础页面结构,提供了 appBar、body、bottomNavigationBar 等常用布局位置。这里我们只用到了 appBar 和 body。
AppBar 的配置很简单,只设置了 title 为"扫描设置"。因为这个页面是从设置主页面跳转过来的,Flutter 会自动添加返回按钮,不需要我们手动处理。title 使用 const Text 可以获得编译时优化。
body 部分使用 ListView 作为容器,这样当内容超出屏幕时可以滚动。padding 设置为 16.w,也就是四周都有 16 个逻辑像素的内边距。使用 .w 单位可以让内边距根据屏幕宽度自适应。
ListView 的 children 中首先是一个 Card 组件,它会给内容添加圆角和阴影效果,让设置项看起来更有层次感。Card 内部使用 Column 垂直排列多个设置项。
扫描提示音开关
第一个设置项是扫描提示音:
Obx(() => SwitchListTile(
title: const Text('扫描提示音'),
subtitle: const Text('扫描成功时播放提示音'),
value: storage.soundEnabled.value,
onChanged: (_) => storage.toggleSound(),
)),
这段代码展示了 GetX 响应式编程的典型用法。Obx 是 GetX 提供的响应式组件,它会自动监听内部使用的响应式变量,当变量值变化时自动重建。这里监听的是 storage.soundEnabled,当用户切换开关时,soundEnabled 的值会变化,Obx 检测到变化后会重建 SwitchListTile,更新开关的显示状态。
SwitchListTile 是 Material Design 的标准开关列表项组件,它组合了 ListTile 和 Switch,非常适合用于设置页面。title 是主标题"扫描提示音",subtitle 是副标题说明"扫描成功时播放提示音",让用户清楚这个开关的作用。
value 属性绑定到 storage.soundEnabled.value,注意这里要用 .value 获取实际的布尔值。onChanged 回调在用户切换开关时触发,我们调用 storage.toggleSound() 方法来切换状态。参数 _ 表示我们不需要使用回调传入的新值,因为 toggleSound 方法内部会自己处理取反逻辑。
分隔线的添加
设置项之间需要分隔线来区分:
const Divider(height: 1),
Divider 是 Material Design 的分隔线组件,height 设置为 1 表示分隔线只占用 1 像素的高度,看起来更精致。使用 const 修饰可以让 Flutter 复用这个组件实例,提高性能。
分隔线的作用是在视觉上区分不同的设置项,让用户更容易找到想要修改的选项。如果没有分隔线,多个设置项挤在一起会显得很拥挤,用户体验不好。
震动反馈开关
第二个设置项是震动反馈:
Obx(() => SwitchListTile(
title: const Text('震动反馈'),
subtitle: const Text('扫描成功时震动提示'),
value: storage.vibrationEnabled.value,
onChanged: (_) => storage.toggleVibration(),
)),
const Divider(height: 1),
震动反馈开关的实现和提示音开关完全一样,只是绑定的状态变量不同。这里绑定的是 storage.vibrationEnabled,调用的方法是 storage.toggleVibration()。
这种一致的实现方式让代码更容易维护。如果以后需要修改开关的样式或行为,只需要改一处就能影响所有开关。同时,用户在使用时也会感到熟悉,因为所有开关的交互方式都是一样的。
震动反馈在实际项目中需要调用系统 API 来实现。Flutter 中可以使用 HapticFeedback 类或者 vibration 插件来触发震动。在 OpenHarmony 平台上,需要确保震动功能的兼容性。
自动打开链接开关
第三个设置项是自动打开链接:
Obx(() => SwitchListTile(
title: const Text('自动打开链接'),
subtitle: const Text('扫描到网址时自动打开浏览器'),
value: storage.autoOpenLinks.value,
onChanged: (_) => storage.toggleAutoOpenLinks(),
)),
const Divider(height: 1),
自动打开链接是一个需要谨慎处理的功能。虽然它能提升用户体验,但也存在安全风险。如果用户扫描到一个恶意网址,自动打开可能会导致钓鱼攻击或恶意软件下载。
因此,这个选项默认是关闭的,让用户自己决定是否开启。在 subtitle 中明确说明了功能的作用,让用户在开启前了解可能的风险。
在实际实现中,即使开启了自动打开,也应该做一些基本的安全检查,比如检查 URL 是否是 HTTPS 协议,是否在已知的恶意网站黑名单中等。
保存到历史开关
最后一个设置项是保存到历史:
Obx(() => SwitchListTile(
title: const Text('保存到历史'),
subtitle: const Text('自动保存扫描记录'),
value: storage.saveToHistory.value,
onChanged: (_) => storage.toggleSaveToHistory(),
)),
],
),
),
],
),
);
}
}
保存到历史功能对大多数用户来说是必需的,所以默认是开启的。但有些用户可能出于隐私考虑不希望保留扫描记录,提供关闭选项可以满足这部分用户的需求。
这里的 Column 和 Card 在最后一个设置项后闭合,ListView 的 children 列表也在这里结束。整个页面的结构是:Scaffold > ListView > Card > Column > 多个 SwitchListTile。
这种嵌套结构在 Flutter 中很常见,虽然看起来层级较深,但每一层都有明确的职责:Scaffold 提供页面框架,ListView 提供滚动能力,Card 提供视觉分组,Column 提供垂直布局,SwitchListTile 提供具体的交互控件。
StorageService 中的状态定义
扫描设置的状态存储在 StorageService 中,来看一下相关的代码:
class StorageService extends GetxService {
final RxBool soundEnabled = true.obs;
final RxBool vibrationEnabled = true.obs;
final RxBool autoOpenLinks = false.obs;
final RxBool saveToHistory = true.obs;
StorageService 继承自 GetxService,这是 GetX 提供的服务基类。和普通的 GetxController 不同,GetxService 不会被自动销毁,适合用于全局服务。
四个设置项都定义为 RxBool 类型,这是 GetX 的响应式布尔类型。.obs 是 GetX 的扩展方法,可以把普通值转换为响应式对象。当这些值变化时,所有监听它们的 Obx 组件都会自动更新。
默认值的设置是经过考虑的:soundEnabled 和 vibrationEnabled 默认开启,因为大多数用户需要扫描反馈;autoOpenLinks 默认关闭,因为有安全风险;saveToHistory 默认开启,因为历史记录功能很常用。
状态切换方法的实现
StorageService 中还定义了切换状态的方法:
void toggleSound() => soundEnabled.value = !soundEnabled.value;
void toggleVibration() => vibrationEnabled.value = !vibrationEnabled.value;
void toggleAutoOpenLinks() => autoOpenLinks.value = !autoOpenLinks.value;
void toggleSaveToHistory() => saveToHistory.value = !saveToHistory.value;
这四个方法都使用箭头函数的简洁写法,每个方法只做一件事:把对应的布尔值取反。使用 .value 属性来修改响应式变量的值,这样才能触发 UI 更新。
如果直接写 soundEnabled = (!soundEnabled.value).obs,虽然语法上没问题,但不会触发响应式更新,因为这是在创建一个新的响应式对象,而不是修改现有对象的值。
这种设计遵循了单一职责原则,每个方法只负责一个设置项的切换。如果以后需要在切换时执行额外的逻辑,比如记录日志或同步到服务器,只需要修改对应的方法即可。
设置持久化的考虑
当前的实现把设置存储在内存中,应用关闭后设置会丢失。在实际项目中,需要把设置持久化到本地存储。可以使用 SharedPreferences 来实现:
import 'package:shared_preferences/shared_preferences.dart';
class StorageService extends GetxService {
late SharedPreferences _prefs;
final RxBool soundEnabled = true.obs;
final RxBool vibrationEnabled = true.obs;
final RxBool autoOpenLinks = false.obs;
final RxBool saveToHistory = true.obs;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
soundEnabled.value = _prefs.getBool('soundEnabled') ?? true;
vibrationEnabled.value = _prefs.getBool('vibrationEnabled') ?? true;
autoOpenLinks.value = _prefs.getBool('autoOpenLinks') ?? false;
saveToHistory.value = _prefs.getBool('saveToHistory') ?? true;
}
SharedPreferences 是 Flutter 官方提供的本地存储插件,支持存储基本类型的数据。init 方法在应用启动时调用,从本地存储读取之前保存的设置值。?? 是空值合并运算符,如果读取的值为 null(比如第一次运行应用),就使用默认值。
保存设置到本地
切换设置时需要同时保存到本地:
void toggleSound() {
soundEnabled.value = !soundEnabled.value;
_prefs.setBool('soundEnabled', soundEnabled.value);
}
void toggleVibration() {
vibrationEnabled.value = !vibrationEnabled.value;
_prefs.setBool('vibrationEnabled', vibrationEnabled.value);
}
void toggleAutoOpenLinks() {
autoOpenLinks.value = !autoOpenLinks.value;
_prefs.setBool('autoOpenLinks', autoOpenLinks.value);
}
void toggleSaveToHistory() {
saveToHistory.value = !saveToHistory.value;
_prefs.setBool('saveToHistory', saveToHistory.value);
}
每个 toggle 方法在修改内存中的值后,还会调用 _prefs.setBool 把新值保存到本地。这样即使应用关闭重启,设置也会保持用户上次的选择。
SharedPreferences 的写入操作是异步的,但我们不需要等待它完成,因为内存中的值已经更新了,UI 会立即响应。即使写入失败,也只是下次启动时会恢复默认值,不会影响当前的使用。
扫描时使用设置
在扫描控制器中,需要根据设置来决定扫描成功后的行为:
class ScanController extends GetxController {
final _storage = Get.find<StorageService>();
final _historyService = Get.find<QrHistoryService>();
void onCodeScanned(String code) {
// 播放提示音
if (_storage.soundEnabled.value) {
_playBeepSound();
}
// 震动反馈
if (_storage.vibrationEnabled.value) {
HapticFeedback.mediumImpact();
}
// 保存到历史
if (_storage.saveToHistory.value) {
final record = QrRecord(
content: code,
type: QrRecord.detectType(code),
);
_historyService.addScanRecord(record);
}
在 onCodeScanned 方法中,根据各个设置项的值来决定是否执行对应的操作。_storage.soundEnabled.value 获取当前的设置值,如果为 true 就播放提示音。
HapticFeedback.mediumImpact() 是 Flutter 提供的触觉反馈 API,会触发一次中等强度的震动。这个 API 在大多数平台上都能正常工作,包括 OpenHarmony。
保存到历史的逻辑也类似,只有当 saveToHistory 为 true 时才创建记录并保存。这样用户关闭保存功能后,扫描记录就不会被保留。
自动打开链接的处理
自动打开链接需要特殊处理:
// 自动打开链接
if (_storage.autoOpenLinks.value &&
QrRecord.detectType(code) == QrType.url) {
_openUrl(code);
} else {
// 跳转到结果页面
Get.toNamed(Routes.SCAN_RESULT, arguments: record);
}
}
Future<void> _openUrl(String url) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
}
}
自动打开链接有两个条件:一是用户开启了 autoOpenLinks 设置,二是扫描到的内容确实是网址类型。只有同时满足这两个条件才会自动打开浏览器,否则跳转到结果页面让用户自己决定。
canLaunchUrl 和 launchUrl 是 url_launcher 插件提供的方法。canLaunchUrl 检查设备是否能打开这个 URL,launchUrl 执行实际的打开操作。LaunchMode.externalApplication 表示在外部应用(浏览器)中打开,而不是在应用内嵌的 WebView 中。
添加更多设置项
如果需要添加更多的扫描设置,可以按照相同的模式扩展。比如添加一个"连续扫描"设置:
// 在 StorageService 中添加
final RxBool continuousScan = false.obs;
void toggleContinuousScan() {
continuousScan.value = !continuousScan.value;
_prefs.setBool('continuousScan', continuousScan.value);
}
// 在 ScanSettingsView 中添加
Obx(() => SwitchListTile(
title: const Text('连续扫描'),
subtitle: const Text('扫描成功后继续扫描下一个'),
value: storage.continuousScan.value,
onChanged: (_) => storage.toggleContinuousScan(),
)),
连续扫描功能在批量扫描场景下很有用,用户不需要每次扫描后都点击"继续扫描"按钮。开启后,扫描成功会显示一个短暂的提示,然后自动继续扫描下一个二维码。
设置项的分组
当设置项较多时,可以按功能分组显示:
body: ListView(
padding: EdgeInsets.all(16.w),
children: [
// 反馈设置组
Text('扫描反馈', style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
)),
SizedBox(height: 8.h),
Card(
child: Column(
children: [
// 提示音和震动开关
],
),
),
SizedBox(height: 16.h),
// 行为设置组
Text('扫描行为', style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
)),
SizedBox(height: 8.h),
Card(
child: Column(
children: [
// 自动打开链接和保存历史开关
],
),
),
],
),
分组显示让设置页面更有条理,用户可以快速找到想要修改的设置。每个分组有一个标题,使用较小的字号和灰色,不会太抢眼但能起到分隔作用。
设置说明的完善
对于一些可能引起困惑的设置,可以添加更详细的说明:
Obx(() => SwitchListTile(
title: const Text('自动打开链接'),
subtitle: const Text('扫描到网址时自动打开浏览器'),
value: storage.autoOpenLinks.value,
onChanged: (_) => storage.toggleAutoOpenLinks(),
)),
if (storage.autoOpenLinks.value)
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: Row(
children: [
Icon(Icons.warning_amber, size: 16.sp, color: Colors.orange),
SizedBox(width: 8.w),
Expanded(
child: Text(
'注意:自动打开链接可能存在安全风险,请确保扫描的是可信来源的二维码。',
style: TextStyle(fontSize: 12.sp, color: Colors.orange),
),
),
],
),
),
当用户开启自动打开链接时,显示一个警告提示,提醒用户注意安全风险。这种条件显示的提示可以帮助用户做出更明智的选择,同时不会在关闭状态下占用空间。
恢复默认设置
提供一个恢复默认设置的功能:
// 在页面底部添加
SizedBox(height: 24.h),
Center(
child: TextButton(
onPressed: () => _showResetDialog(context, storage),
child: Text(
'恢复默认设置',
style: TextStyle(color: Colors.grey[600]),
),
),
),
void _showResetDialog(BuildContext context, StorageService storage) {
Get.dialog(
AlertDialog(
title: const Text('恢复默认设置'),
content: const Text('确定要将所有扫描设置恢复为默认值吗?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('取消'),
),
TextButton(
onPressed: () {
storage.resetScanSettings();
Get.back();
Get.snackbar('成功', '已恢复默认设置',
snackPosition: SnackPosition.BOTTOM);
},
child: const Text('确定'),
),
],
),
);
}
恢复默认设置功能让用户可以快速回到初始状态,不需要一个个手动调整。点击后会弹出确认对话框,避免误操作。确认后调用 storage.resetScanSettings() 方法重置所有设置。
扫描框样式设置
可以让用户自定义扫描框的样式:
Card(
child: Column(
children: [
ListTile(
leading: const Icon(Icons.crop_square),
title: const Text('扫描框样式'),
subtitle: const Text('选择扫描框的外观'),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFrameStyleOption(storage, 'square', '方形'),
_buildFrameStyleOption(storage, 'rounded', '圆角'),
_buildFrameStyleOption(storage, 'corners', '四角'),
],
)),
),
],
),
),
Widget _buildFrameStyleOption(StorageService storage, String style, String label) {
final isSelected = storage.scanFrameStyle.value == style;
return GestureDetector(
onTap: () => storage.scanFrameStyle.value = style,
child: Column(
children: [
Container(
width: 50.w,
height: 50.w,
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? Theme.of(Get.context!).primaryColor : Colors.grey,
width: isSelected ? 2 : 1,
),
borderRadius: style == 'rounded' ? BorderRadius.circular(8.r) : null,
),
child: style == 'corners' ? _buildCornersDecoration() : null,
),
SizedBox(height: 4.h),
Text(
label,
style: TextStyle(
color: isSelected ? Theme.of(Get.context!).primaryColor : Colors.grey,
fontSize: 12.sp,
),
),
],
),
);
}
提供三种扫描框样式:方形、圆角、四角。用户可以根据喜好选择。
扫描线动画设置
扫描线的动画效果也可以自定义:
Obx(() => SwitchListTile(
title: const Text('扫描线动画'),
subtitle: const Text('显示扫描线上下移动效果'),
value: storage.showScanLine.value,
onChanged: (value) => storage.showScanLine.value = value,
)),
Obx(() => storage.showScanLine.value
? ListTile(
title: const Text('动画速度'),
subtitle: Slider(
value: storage.scanLineSpeed.value,
min: 0.5,
max: 2.0,
divisions: 3,
label: _getSpeedLabel(storage.scanLineSpeed.value),
onChanged: (value) => storage.scanLineSpeed.value = value,
),
)
: const SizedBox.shrink(),
),
String _getSpeedLabel(double speed) {
if (speed <= 0.75) return '慢';
if (speed <= 1.25) return '正常';
return '快';
}
用户可以选择是否显示扫描线,以及调整扫描线的移动速度。
扫描成功反馈设置
扫描成功后的反馈方式可以自定义:
Card(
child: Column(
children: [
const ListTile(
leading: Icon(Icons.feedback),
title: Text('扫描成功反馈'),
),
Obx(() => CheckboxListTile(
title: const Text('震动反馈'),
value: storage.vibrateOnScan.value,
onChanged: (value) => storage.vibrateOnScan.value = value ?? true,
)),
Obx(() => CheckboxListTile(
title: const Text('声音提示'),
value: storage.soundOnScan.value,
onChanged: (value) => storage.soundOnScan.value = value ?? true,
)),
Obx(() => CheckboxListTile(
title: const Text('闪光提示'),
value: storage.flashOnScan.value,
onChanged: (value) => storage.flashOnScan.value = value ?? false,
)),
],
),
),
提供三种反馈方式:震动、声音、闪光。用户可以根据使用场景选择合适的反馈方式。
扫描历史设置
关于扫描历史的一些设置:
Card(
child: Column(
children: [
const ListTile(
leading: Icon(Icons.history),
title: Text('历史记录'),
),
Obx(() => SwitchListTile(
title: const Text('保存扫描历史'),
subtitle: const Text('自动保存每次扫描结果'),
value: storage.saveScanHistory.value,
onChanged: (value) => storage.saveScanHistory.value = value,
)),
Obx(() => storage.saveScanHistory.value
? ListTile(
title: const Text('历史记录上限'),
subtitle: Text('最多保存 ${storage.maxHistoryCount.value} 条'),
trailing: DropdownButton<int>(
value: storage.maxHistoryCount.value,
items: [50, 100, 200, 500].map((count) => DropdownMenuItem(
value: count,
child: Text('$count 条'),
)).toList(),
onChanged: (value) {
if (value != null) storage.maxHistoryCount.value = value;
},
),
)
: const SizedBox.shrink(),
),
],
),
),
用户可以选择是否保存扫描历史,以及设置历史记录的上限数量。
高级扫描设置
一些高级用户可能需要的设置:
ExpansionTile(
title: const Text('高级设置'),
leading: const Icon(Icons.settings_applications),
children: [
Obx(() => SwitchListTile(
title: const Text('多码识别'),
subtitle: const Text('同时识别画面中的多个二维码'),
value: storage.multiCodeScan.value,
onChanged: (value) => storage.multiCodeScan.value = value,
)),
Obx(() => SwitchListTile(
title: const Text('反色识别'),
subtitle: const Text('识别白底黑码的反色二维码'),
value: storage.invertedScan.value,
onChanged: (value) => storage.invertedScan.value = value,
)),
Obx(() => ListTile(
title: const Text('扫描精度'),
subtitle: Text(_getPrecisionLabel(storage.scanPrecision.value)),
trailing: Slider(
value: storage.scanPrecision.value,
min: 1,
max: 3,
divisions: 2,
onChanged: (value) => storage.scanPrecision.value = value,
),
)),
],
),
String _getPrecisionLabel(double precision) {
if (precision <= 1.5) return '快速(可能漏识别)';
if (precision <= 2.5) return '平衡';
return '精确(较慢)';
}
高级设置用ExpansionTile折叠起来,不影响普通用户的使用体验。
小结
这篇文章详细介绍了扫描设置页面的实现。从功能规划开始,介绍了四个核心设置项的设计思路,然后逐步实现了页面布局、响应式状态管理、设置持久化等功能。
扫描设置虽然看起来简单,但涉及到用户体验、安全性、数据持久化等多个方面。好的设置页面应该让用户一目了然,知道每个选项的作用,同时提供合理的默认值,让大多数用户不需要修改就能获得良好的体验。
通过 GetX 的响应式编程,我们实现了设置状态的自动同步,用户切换开关后 UI 会立即更新,扫描行为也会相应改变。这种声明式的编程方式让代码更简洁,也更容易维护。
扩展功能方面,扫描框样式、扫描线动画、成功反馈、历史记录设置、高级扫描选项都能进一步提升用户体验,满足不同用户的个性化需求。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)