Flutter for OpenHarmony生活助手App实战:设置功能实现
本文介绍了Flutter设置页面的设计与实现。作者从设置页面的价值出发,分析了其个性化需求、功能开关和账户管理等核心功能。在实现上,采用分组管理方式区分通知、外观、账户等设置项,通过StatefulWidget管理开关状态,使用ListView展示可滚动内容。具体实现包括:分组标题设计、开关型设置项(使用Switch组件)、选项型设置项(带箭头导航)以及危险操作的警示设计。文章提供了完整的代码示例

说起设置页面,很多人觉得这是个不起眼的功能,无非就是几个开关和选项。但我自己做过几个App后发现,设置页面其实很重要。它是用户个性化应用的入口,也是很多高级功能的藏身之处。一个好的设置页面,能让用户用得更舒服。
为什么设置功能不可或缺
在开始写代码之前,我先想清楚了设置页面的价值。
第一个是个性化需求。每个人的使用习惯不一样,有人喜欢深色模式,有人喜欢浅色模式;有人喜欢声音提醒,有人觉得吵。设置页面就是让用户按自己的喜好调整应用。
第二个是功能开关。有些功能不是所有人都需要,比如推送通知。有人觉得很有用,有人觉得很烦。通过设置页面,用户可以自己决定开启还是关闭。
第三个是账户管理。修改密码、清除缓存、退出登录这些操作,都需要一个入口。设置页面就是这个入口。
功能设计的思路
在设计这个功能时,我考虑了以下几个方面。
分组管理
设置项很多,如果全部堆在一起,会很乱。我把设置项分成了几组:通知设置、外观设置、账户设置、其他。每组有个标题,看起来更清晰。
开关和选项的区分
有些设置是开关型的,比如"推送通知",只有开和关两种状态。有些设置是选项型的,比如"字体大小",有小、中、大三种选择。这两种设置的交互方式不同,要分别处理。
危险操作的警示
"退出登录"是个危险操作,点错了可能会丢失数据。所以我用红色按钮,放在最下面,和其他设置区分开。
页面整体结构
先看页面的基本框架:
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool notificationEnabled = true;
bool darkModeEnabled = false;
bool soundEnabled = true;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设置'),
),
body: ListView(
children: [
_buildSection('通知设置'),
_buildSwitchTile('推送通知', '接收应用推送通知', Icons.notifications, notificationEnabled, (value) => setState(() => notificationEnabled = value)),
_buildSwitchTile('声音提醒', '开启声音提醒', Icons.volume_up, soundEnabled, (value) => setState(() => soundEnabled = value)),
_buildSection('外观设置'),
_buildSwitchTile('深色模式', '开启深色主题', Icons.dark_mode, darkModeEnabled, (value) => setState(() => darkModeEnabled = value)),
_buildTile('字体大小', '中', Icons.text_fields, () {}),
_buildSection('账户设置'),
_buildTile('修改密码', '', Icons.lock, () {}),
_buildTile('清除缓存', '125 MB', Icons.cleaning_services, () {}),
_buildSection('其他'),
_buildTile('隐私政策', '', Icons.privacy_tip, () {}),
_buildTile('用户协议', '', Icons.description, () {}),
SizedBox(height: 20.h),
_buildLogoutButton(),
SizedBox(height: 20.h),
],
),
);
}
}
StatefulWidget的必要性
设置页面用了StatefulWidget,因为开关的状态会变化。用户点击开关,界面要更新。如果用StatelessWidget,就没法响应用户的操作。
状态变量的设计
定义了三个布尔变量,对应三个开关:
notificationEnabled:推送通知是否开启darkModeEnabled:深色模式是否开启soundEnabled:声音提醒是否开启
这些变量的初始值根据实际需求设置。比如推送通知默认开启,深色模式默认关闭。
ListView的使用
设置项用ListView展示,这样内容超出屏幕时可以滚动。不用Column+SingleChildScrollView,因为ListView更适合这种场景。
分组标题的设计
每组设置项上方有个标题,用灰色小字显示:
Widget _buildSection(String title) {
return Padding(
padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
);
}
Padding的设置
标题的上边距是20,下边距是8,左右边距是16。这样标题和上一组设置项有足够的间隔,和下一组设置项又不会太远。
文字样式的选择
标题用14号字,灰色,加粗。灰色表示这是辅助信息,不是主要内容。加粗让标题更醒目,和设置项区分开。
开关型设置项的实现
开关型设置项是最常见的,比如推送通知、深色模式:
Widget _buildSwitchTile(
String title,
String subtitle,
IconData icon,
bool value,
Function(bool) onChanged,
) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: ListTile(
leading: Icon(icon, color: Colors.blue),
title: Text(title),
subtitle: Text(subtitle, style: TextStyle(fontSize: 12.sp)),
trailing: Switch(
value: value,
onChanged: onChanged,
),
),
);
}
ListTile的使用
ListTile是Flutter提供的标准列表项组件,包含leading、title、subtitle、trailing四个部分。用它来构建设置项,代码很简洁。
leading图标的设计
每个设置项左边有个图标,用蓝色显示。图标能让用户快速识别设置项的类型,比如看到铃铛图标就知道是通知设置。
title和subtitle的层次
title是设置项的名称,用默认大小的字体。subtitle是设置项的说明,用12号小字,灰色。这种层次关系让信息更清晰。
Switch的交互
右边是一个开关,用户点击可以切换状态。value是当前状态,onChanged是状态改变时的回调。
在回调里调用setState,更新状态变量。这样开关的视觉状态就会跟着变化。
Container的装饰
每个设置项用白色卡片包裹,圆角12。上下边距4,左右边距16。这样设置项之间有间隔,不会挤在一起。
选项型设置项的实现
选项型设置项右边不是开关,而是当前选中的值和箭头:
Widget _buildTile(String title, String trailing, IconData icon, VoidCallback onTap) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: ListTile(
leading: Icon(icon, color: Colors.blue),
title: Text(title),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (trailing.isNotEmpty)
Text(
trailing,
style: TextStyle(fontSize: 14.sp, color: Colors.grey),
),
SizedBox(width: 8.w),
const Icon(Icons.chevron_right, color: Colors.grey),
],
),
onTap: onTap,
),
);
}
trailing的动态显示
trailing参数是当前选中的值,比如"中"表示字体大小是中号。如果trailing不为空,就显示这个值;如果为空,就只显示箭头。
箭头的含义
右边的箭头表示这个设置项可以点击,点击后会进入详细页面或弹出选择器。这是一种常见的交互暗示。
onTap的处理
点击设置项时,调用onTap回调。在回调里可以跳转到详细页面,或者弹出对话框让用户选择。
比如点击"字体大小",可以弹出一个对话框,让用户选择小、中、大:
void _showFontSizeDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('选择字体大小'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile(
title: const Text('小'),
value: 'small',
groupValue: _fontSize,
onChanged: (value) {
setState(() => _fontSize = value as String);
Navigator.pop(context);
},
),
RadioListTile(
title: const Text('中'),
value: 'medium',
groupValue: _fontSize,
onChanged: (value) {
setState(() => _fontSize = value as String);
Navigator.pop(context);
},
),
RadioListTile(
title: const Text('大'),
value: 'large',
groupValue: _fontSize,
onChanged: (value) {
setState(() => _fontSize = value as String);
Navigator.pop(context);
},
),
],
),
),
);
}
清除缓存的实现
"清除缓存"这个设置项比较特殊,右边显示的是缓存大小:
_buildTile('清除缓存', '125 MB', Icons.cleaning_services, () {
_showClearCacheDialog();
})
点击后,应该弹出确认对话框,询问用户是否确定清除:
void _showClearCacheDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('清除缓存'),
content: const Text('确定要清除所有缓存吗?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
// 清除缓存的逻辑
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('缓存已清除')),
);
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('清除'),
),
],
),
);
}
确认对话框的必要性
清除缓存是不可逆的操作,必须让用户确认。对话框的内容要明确告诉用户操作的后果。
清除按钮的颜色
清除按钮用红色,警示用户这是危险操作。和取消按钮的默认颜色形成对比。
操作反馈
清除完成后,用SnackBar显示提示:“缓存已清除”。这样用户知道操作成功了。
退出登录按钮的设计
退出登录按钮放在最下面,用红色,很醒目:
Widget _buildLogoutButton() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: ElevatedButton(
onPressed: () {
_showLogoutDialog();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
child: Text(
'退出登录',
style: TextStyle(fontSize: 16.sp, color: Colors.white),
),
),
);
}
红色的警示作用
退出登录是个重要操作,用红色按钮能引起用户注意。红色在视觉上有警示作用,让用户知道这不是普通操作。
按钮的大小
按钮的垂直padding是16,比普通按钮大一些。这样按钮更醒目,也更容易点击。
确认对话框
点击退出登录后,应该弹出确认对话框:
void _showLogoutDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('退出登录'),
content: const Text('确定要退出登录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
// 退出登录的逻辑
Navigator.pop(context);
// 跳转到登录页面
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('退出'),
),
],
),
);
}
深色模式的实现
深色模式是个很受欢迎的功能,实现起来需要配合主题管理。
主题的切换
在MaterialApp里定义两套主题:
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: _darkModeEnabled ? ThemeMode.dark : ThemeMode.light,
// ...
)
当用户切换深色模式开关时,更新_darkModeEnabled变量,整个应用的主题就会跟着变化。
状态的持久化
用户的设置应该保存到本地,这样下次打开应用时,设置还在。可以用SharedPreferences:
Future<void> _saveDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('darkMode', value);
}
Future<void> _loadDarkMode() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_darkModeEnabled = prefs.getBool('darkMode') ?? false;
});
}
在initState里调用_loadDarkMode,加载保存的设置。在开关的onChanged里调用_saveDarkMode,保存新的设置。
通知权限的处理
推送通知需要用户授权,不能直接开启。应该先检查权限,如果没有权限,引导用户去设置:
Future<void> _toggleNotification(bool value) async {
if (value) {
// 检查通知权限
final status = await Permission.notification.status;
if (status.isDenied) {
// 请求权限
final result = await Permission.notification.request();
if (result.isDenied) {
// 用户拒绝了权限
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('需要通知权限才能开启推送')),
);
return;
}
}
}
setState(() {
notificationEnabled = value;
});
await _saveNotificationSetting(value);
}
这样处理更规范,避免用户开启了推送但没有权限,导致收不到通知。
实际使用体验
这个设置页面我自己用了一段时间,感觉很方便。想调整什么设置,一眼就能找到。
分组管理很清晰,通知相关的设置在一起,外观相关的设置在一起,不会乱。
开关的交互很流畅,点一下就切换了,不需要额外的确认步骤。选项型设置点击后弹出选择器,也很直观。
退出登录按钮用红色,很醒目。我有次想退出登录,一眼就看到了,不用在菜单里找。
可以改进的地方
如果要做得更完善,可以考虑以下几点。
搜索功能
设置项多了以后,可以加个搜索功能。用户输入关键词,快速找到想要的设置项。
设置的分类更细
可以把设置分成更多类别,比如"隐私设置"、“数据设置”、"显示设置"等。每个类别单独一个页面,设置页面只显示类别入口。
设置的说明更详细
有些设置比较复杂,可以加个问号图标,点击后显示详细说明。这样用户能更好地理解每个设置的作用。
设置的预设方案
提供几套预设方案,比如"省电模式"、“性能模式”、“平衡模式”。用户可以一键切换,不用逐个调整设置。
设置的导入导出
用户可能在多个设备上使用,设置应该能同步。可以加个导入导出功能,把设置导出成文件,在其他设备上导入。
小结
今天实现了设置功能,用到了开关、列表、对话框等组件。核心是用分组管理设置项,用开关和选项两种交互方式,让用户能方便地调整应用。
这个功能虽然不是最核心的,但对用户体验影响很大。一个好的设置页面,能让用户用得更舒服,更符合自己的习惯。
在实现过程中,我特别注重信息的组织。分组标题、图标、说明文字,这些细节都是为了让用户快速找到想要的设置。危险操作用红色警示,也是为了避免用户误操作。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)