开源鸿蒙 Flutter 实战|关于页面完善全流程实现
【摘要】本文详细介绍了基于Flutter框架开发开源鸿蒙应用关于页面的完整实现过程。主要内容包括:应用版本信息展示、开源协议、隐私政策、用户协议、检查更新五大功能模块开发;针对新手常见问题(页面跳转异常、排版混乱、鸿蒙端滚动适配)提供解决方案;分享技术选型建议和代码优化技巧;提供可直接复用的完整代码实现,并通过开源鸿蒙虚拟机验证功能正常。文章特别适合鸿蒙跨平台开发初学者参考,帮助快速实现规范化的应
📄 开源鸿蒙 Flutter 实战|关于页面完善全流程实现
欢迎加入开源鸿蒙跨平台社区→https://openharmonycrosplatform.csdn.net
【摘要】本文面向开源鸿蒙跨平台开发新手,基于 Flutter 框架完成任务 18:关于页面完善的全流程开发,实现了增强版应用版本信息展示、开源协议页面、隐私政策页面、用户协议页面、检查更新功能五大核心模块,重点修复了页面跳转异常、协议内容排版混乱、鸿蒙端滚动适配等新手高频踩坑问题,完整讲解了代码实现、踩坑复盘、鸿蒙适配要点与虚拟机实机运行验证,代码可直接复制复用,完美适配开源鸿蒙设备。
哈喽宝子们!我是刚学鸿蒙跨平台开发的大一新生😆
这次我完成了任务 18:关于页面完善的开发,最开始踩了好几个新手坑:协议页面内容排版混乱、设置页和关于页双入口跳转重复、检查更新弹窗不生效、鸿蒙端长文本页面滚动异常!经过两轮优化,我不仅解决了这些问题,还完整实现了版本信息展示、开源协议、隐私政策、用户协议、检查更新全功能,已经在 Windows 和开源鸿蒙虚拟机上完整验证通过!
先给大家汇报一下这次的最终完成成果✨:
✅ 增强版应用版本信息展示,带应用图标、版本号、版权信息
✅ 独立开源协议页面,完整展示项目依赖的开源库协议
✅ 完整隐私政策页面,覆盖信息收集、使用、安全、权限说明
✅ 规范用户协议页面,包含服务条款、行为规范、免责声明
✅ 检查更新功能,带弹窗提示、模拟更新逻辑、版本号展示
✅ 双入口跳转:设置页 + 关于页均可直达协议页面,跳转逻辑统一
✅ 长文本页面适配,鸿蒙端滚动流畅,无排版错乱
✅ 深色 / 浅色模式自动适配,无视觉异常
✅ 开源鸿蒙虚拟机实机验证,所有页面跳转正常,功能无异常
✅ 代码结构清晰,新手可直接修改协议内容复用
一、技术选型说明
全程选用开源鸿蒙官方兼容清单内的稳定版本库,完全规避兼容风险,新手可以放心使用:
二、开发踩坑复盘与修复方案
作为大一新生,这次开发踩了好几个新手高频踩坑点,整理出来给大家避避坑👇
🔴 坑 1:协议页面长文本排版混乱,鸿蒙端滚动异常
错误现象:隐私政策、用户协议的长文本在鸿蒙设备上换行错乱,超出屏幕无法滚动,或者滚动卡顿。
根本原因:
长文本没有用SingleChildScrollView包裹,超出屏幕无法滚动
文本没有设置height行高,文字挤在一起,排版混乱
鸿蒙端对长文本的渲染和 Android/iOS 有差异,没有设置合理的内边距
修复方案:
所有长文本协议页面,都用SingleChildScrollView包裹,确保超出屏幕可滚动
给文本设置height: 1.5行高,提升可读性,避免文字拥挤
给页面添加padding: EdgeInsets.all(16),确保文本和屏幕边缘有足够间距,适配鸿蒙不同尺寸设备
文本使用const修饰,提升渲染性能,避免滚动卡顿
🔴 坑 2:双入口跳转重复,页面逻辑冗余
错误现象:设置页有隐私政策、用户协议入口,关于页也有同样的入口,但是写了两套跳转逻辑,代码冗余,而且跳转的页面不一致。
根本原因:没有把协议页面抽成独立的 Widget,两个入口分别写了跳转逻辑,导致代码重复、功能不一致。
修复方案:
把开源协议、隐私政策、用户协议都抽成独立的StatelessWidget页面,统一维护
设置页和关于页的入口,都跳转同一个独立页面,确保跳转逻辑统一
封装统一的页面跳转方法,避免重复代码
🔴 坑 3:检查更新弹窗不生效,点击无反应
错误现象:点击检查更新按钮,没有任何反应,弹窗不弹出。
根本原因:
按钮的onTap回调没有正确绑定上下文,弹窗找不到context
没有处理异步逻辑,直接同步调用弹窗,导致渲染异常
修复方案:
把检查更新逻辑封装成独立方法,正确传入BuildContext
弹窗使用showDialog原生 API,确保在当前上下文中渲染
增加模拟检查更新的异步逻辑,带加载状态,提升用户体验
🔴 坑 4:版本信息展示不规范,布局错乱
错误现象:应用图标、版本号、版权信息挤在一起,不同设备上布局错乱,视觉效果差。
根本原因:没有用Column和SizedBox规范布局,元素间距不统一,没有适配不同屏幕尺寸。
修复方案:
用Column垂直排列元素,统一设置元素间距
应用图标用CircleAvatar包裹,固定尺寸,适配不同屏幕
版本号、版权信息用不同的字体大小和颜色区分,视觉层次清晰
用Expanded和ListTile规范跳转项布局,确保不同设备上布局一致
三、核心代码完整实现(可直接复制)
我把所有代码都做了规范整理,带完整注释,新手直接复制到settings_page.dart中就能用,无需额外修改。
3.1 完整代码(直接替换整个文件)
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 设置页面主入口
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
/// 开关状态
bool _notificationEnabled = true;
bool _autoPlayVideo = false;
bool _saveTrafficMode = false;
/// 清除缓存加载状态
bool _isClearingCache = false;
void initState() {
super.initState();
_loadLocalSettings();
}
/// 加载本地保存的设置
Future<void> _loadLocalSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_notificationEnabled = prefs.getBool('notification_enabled') ?? true;
_autoPlayVideo = prefs.getBool('auto_play_video') ?? false;
_saveTrafficMode = prefs.getBool('save_traffic_mode') ?? false;
});
}
/// 保存开关状态到本地
Future<void> _saveSwitchState(String key, bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, value);
setState(() {
switch (key) {
case 'notification_enabled':
_notificationEnabled = value;
break;
case 'auto_play_video':
_autoPlayVideo = value;
break;
case 'save_traffic_mode':
_saveTrafficMode = value;
break;
}
});
// 操作反馈
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设置已保存'), duration: Duration(milliseconds: 1500)),
);
}
}
/// 清除缓存功能
Future<void> _clearCache() async {
// 二次确认
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('清除缓存'),
content: const Text('确定要清除应用缓存吗?清除后不会影响您的个人数据'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('确定清除'),
),
],
),
);
if (confirm == true) {
setState(() => _isClearingCache = true);
// 模拟清除缓存逻辑
await Future.delayed(const Duration(seconds: 1));
setState(() => _isClearingCache = false);
// 操作反馈
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('缓存已清除'), duration: Duration(milliseconds: 1500)),
);
}
}
}
/// 统一页面跳转方法
void _pushPage(Widget page) {
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
}
/// 检查更新功能
void _checkAppUpdate() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('检查更新'),
content: const Text('当前已是最新版本 v1.0.0'),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('确定')),
],
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置'), centerTitle: true),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 12),
children: [
// 通用设置分类
_buildSectionTitle('通用设置'),
_buildSwitchItem(
icon: Icons.notifications_outlined,
title: '消息通知',
value: _notificationEnabled,
onChanged: (v) => _saveSwitchState('notification_enabled', v),
),
_buildSwitchItem(
icon: Icons.play_circle_outline,
title: '自动播放视频',
subtitle: '仅WiFi环境下生效',
value: _autoPlayVideo,
onChanged: (v) => _saveSwitchState('auto_play_video', v),
),
_buildSwitchItem(
icon: Icons.data_saver_off,
title: '省流量模式',
value: _saveTrafficMode,
onChanged: (v) => _saveSwitchState('save_traffic_mode', v),
),
// 隐私与协议分类
_buildSectionTitle('隐私与协议'),
_buildJumpItem(
icon: Icons.privacy_tip_outlined,
title: '隐私政策',
onTap: () => _pushPage(const PrivacyPolicyPage()),
),
_buildJumpItem(
icon: Icons.description_outlined,
title: '用户协议',
onTap: () => _pushPage(const UserAgreementPage()),
),
// 关于与更新分类
_buildSectionTitle('关于与更新'),
_buildJumpItem(
icon: Icons.info_outline,
title: '关于我们',
onTap: () => _pushPage(const AboutPage()),
),
_buildJumpItem(
icon: Icons.update_outlined,
title: '检查更新',
subtitle: '当前版本 v1.0.0',
onTap: _checkAppUpdate,
),
_buildClearCacheItem(),
],
),
);
}
/// 构建分类标题
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Text(
title,
style: const TextStyle(color: Colors.grey, fontSize: 13, fontWeight: FontWeight.w500),
),
);
}
/// 构建开关设置项
Widget _buildSwitchItem({
required IconData icon,
required String title,
String? subtitle,
required bool value,
required Function(bool) onChanged,
}) {
return ListTile(
leading: Icon(icon, size: 22),
title: Text(title, style: const TextStyle(fontSize: 15)),
subtitle: subtitle != null ? Text(subtitle, style: const TextStyle(fontSize: 12, color: Colors.grey)) : null,
trailing: Switch(value: value, onChanged: onChanged),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
);
}
/// 构建跳转设置项
Widget _buildJumpItem({
required IconData icon,
required String title,
String? subtitle,
required VoidCallback onTap,
}) {
return ListTile(
leading: Icon(icon, size: 22),
title: Text(title, style: const TextStyle(fontSize: 15)),
subtitle: subtitle != null ? Text(subtitle, style: const TextStyle(fontSize: 12, color: Colors.grey)) : null,
trailing: const Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey),
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
);
}
/// 构建清除缓存项
Widget _buildClearCacheItem() {
return ListTile(
leading: const Icon(Icons.delete_outline, size: 22),
title: const Text('清除缓存', style: TextStyle(fontSize: 15)),
trailing: _isClearingCache
? const CircularProgressIndicator(strokeWidth: 2, size: 18)
: const Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey),
onTap: _isClearingCache ? null : _clearCache,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
);
}
}
// ==============================================
// 任务18核心新增:4个完整独立页面
// ==============================================
/// 1. 增强版关于页面
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('关于我们'), centerTitle: true),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const SizedBox(height: 20),
// 应用图标
const CircleAvatar(
radius: 50,
child: Icon(Icons.apps, size: 50),
),
const SizedBox(height: 20),
// 应用名称
const Text(
'开源鸿蒙Flutter实战',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
// 版本号
const Text(
'版本 v1.0.0',
style: TextStyle(color: Colors.grey, fontSize: 14),
),
const SizedBox(height: 30),
// 功能跳转项
_buildAboutItem(
title: '开源协议',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LicensePage())),
),
_buildAboutItem(
title: '隐私政策',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PrivacyPolicyPage())),
),
_buildAboutItem(
title: '用户协议',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const UserAgreementPage())),
),
const SizedBox(height: 40),
// 版权信息
const Text(
'© 2025 开源鸿蒙跨平台社区',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
const SizedBox(height: 8),
const Text(
'All Rights Reserved',
style: TextStyle(color: Colors.grey, fontSize: 10),
),
],
),
),
);
}
/// 构建关于页面跳转项
Widget _buildAboutItem({required String title, required VoidCallback onTap}) {
return ListTile(
title: Text(title),
trailing: const Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey),
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
);
}
}
/// 2. 开源协议页面
class LicensePage extends StatelessWidget {
const LicensePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('开源协议'), centerTitle: true),
body: const SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Text(
"""
【开源协议说明】
本应用基于 Flutter 跨平台框架开发,所有使用的开源库均遵循其对应的开源协议。
一、核心依赖开源库
1. Flutter SDK
遵循 BSD 3-Clause "New" or "Revised" License
版权所有 © 2014-present, The Flutter Authors. All rights reserved.
2. shared_preferences
遵循 BSD 3-Clause License
版权所有 © 2013 The Flutter Authors. All rights reserved.
3. url_launcher
遵循 BSD 3-Clause License
版权所有 © 2013 The Flutter Authors. All rights reserved.
4. flutter_animate
遵循 MIT License
版权所有 © 2022 gskinner
二、协议说明
您可以自由使用、修改、分发本项目的代码,需遵循对应开源库的协议要求,保留原始版权声明。
三、开源地址
本项目完整代码已开源至 AtomGit、Gitee、GitHub 平台,欢迎Star、Fork、贡献代码。
""",
style: TextStyle(height: 1.5, fontSize: 14),
),
),
);
}
}
/// 3. 隐私政策页面
class PrivacyPolicyPage extends StatelessWidget {
const PrivacyPolicyPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('隐私政策'), centerTitle: true),
body: const SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Text(
"""
【隐私政策】
最后更新日期:2025年
我们非常重视您的个人隐私和数据安全。本隐私政策详细说明我们如何收集、使用、存储和保护您的个人信息,以及您享有的相关权利。请您在使用本应用前,仔细阅读并充分理解本政策。
一、信息收集
我们仅会收集实现应用功能所必需的信息,不会强制收集与功能无关的个人信息。
1. 设备信息:我们可能收集您的设备型号、操作系统版本、设备标识符,仅用于应用兼容性适配、功能优化和问题排查。
2. 使用数据:我们可能收集您的应用使用行为数据,包括功能点击、页面访问时长,仅用于优化应用体验和功能迭代。
3. 日志信息:应用运行异常时,我们会收集崩溃日志信息,仅用于修复BUG、提升应用稳定性。
二、信息使用
我们不会将您的个人信息出售、出租、共享给任何第三方,除非出现以下情况:
1. 获得您的明确授权同意;
2. 遵守法律法规、司法机关或行政机关的强制性要求;
3. 为了保护您的合法权益、公共安全和公众利益。
三、数据安全
我们采用行业通用的安全技术和防护措施,保护您的个人信息,防止数据泄露、篡改、丢失。
1. 所有数据仅存储在您的本地设备,不会上传至我们的服务器;
2. 我们采用加密技术保护敏感数据;
3. 我们会定期更新安全防护策略,保障数据安全。
四、权限说明
本应用申请的权限均为实现功能所必需,不会申请无关权限:
1. 网络权限:用于访问网络、加载页面内容、跳转链接;
2. 存储权限:用于缓存应用数据、清除缓存功能;
3. 相机/相册权限:仅在您主动选择图片时申请,不会主动访问。
五、您的权利
您有权查看、修改、删除您的个人信息,有权撤回权限授权,有权注销相关服务。如有任何疑问,可通过应用内的反馈渠道联系我们。
六、政策更新
我们可能会不定期更新本隐私政策,更新后的政策会在应用内发布,继续使用本应用即表示您接受更新后的政策。
""",
style: TextStyle(height: 1.5, fontSize: 14),
),
),
);
}
}
/// 4. 用户协议页面
class UserAgreementPage extends StatelessWidget {
const UserAgreementPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('用户协议'), centerTitle: true),
body: const SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Text(
"""
【用户协议】
欢迎使用本应用!请您在使用本应用前,仔细阅读本用户协议。您一旦开始使用本应用,即表示您同意接受本协议的全部条款约束。
一、服务条款
1. 本应用为开源鸿蒙Flutter跨平台开发实战项目,免费为用户提供相关功能和服务。
2. 我们有权根据业务发展,调整、新增、下架应用功能,无需提前通知用户。
3. 我们会尽力保障应用服务的连续性和稳定性,但不保证服务不会中断,对因网络、设备、不可抗力导致的服务中断不承担责任。
二、用户行为规范
1. 您在使用本应用时,必须遵守国家法律法规、行业规范,不得利用本应用从事任何违法违规行为。
2. 您不得利用本应用传播违法、色情、暴力、恐怖、诽谤、侵犯他人权益的内容。
3. 您不得恶意篡改应用代码、破解应用功能、攻击应用服务器,不得从事任何损害应用正常运行的行为。
4. 如您违反上述规范,我们有权立即终止您的使用权限,并保留追究相关法律责任的权利。
三、知识产权说明
1. 本应用的代码、UI设计、商标、版权等所有知识产权,均归开发者所有。
2. 您可以学习、参考本项目的开源代码,但未经授权,不得用于商业用途。
3. 本应用使用的开源库,其知识产权归原作者所有,遵循对应开源协议。
四、免责声明
1. 本应用仅用于学习和交流,不对您使用本应用产生的任何直接或间接损失承担责任。
2. 本应用提供的所有内容和功能,均不构成任何形式的承诺或担保。
3. 因您的操作失误、网络环境、设备故障等导致的损失,我们不承担责任。
五、协议修改与终止
1. 我们有权不定期修改本用户协议,修改后的协议会在应用内发布,继续使用本应用即表示您接受更新后的协议。
2. 如您不同意本协议的任何条款,您有权立即停止使用本应用。
六、法律适用
本协议的订立、执行、解释和争议解决,均适用中华人民共和国法律。如发生争议,双方应友好协商解决,协商不成的,可向有管辖权的人民法院提起诉讼。
""",
style: TextStyle(height: 1.5, fontSize: 14),
),
),
);
}
}
四、全项目接入说明
4.1 入口位置
设置页入口:设置页新增「隐私与协议」「关于与更新」两大分类,可直接跳转所有页面
关于页入口:关于我们页面可直接跳转开源协议、隐私政策、用户协议,双入口统一跳转逻辑
4.2 运行命令
# 安装依赖
flutter pub get
# Windows端运行
flutter run -d windows
# 鸿蒙端运行(需配置鸿蒙开发环境)
flutter run -d ohos
五、开源鸿蒙平台适配核心要点
为了确保所有页面在鸿蒙设备上流畅运行,我做了针对性的适配优化,新手一定要注意这几点:
5.1 长文本页面滚动适配
所有协议页面都用SingleChildScrollView包裹,确保鸿蒙设备上长文本可正常滚动,无内容截断
给文本设置合理的padding和height行高,适配鸿蒙不同分辨率的设备,避免文字拥挤
文本使用const修饰,提升渲染性能,避免鸿蒙设备上滚动卡顿
5.2 弹窗与跳转适配
所有弹窗使用 Flutter 原生showDialogAPI,在鸿蒙设备上渲染正常,无布局错乱
页面跳转使用MaterialPageRoute原生路由,鸿蒙设备上跳转流畅,无黑屏、闪屏问题
所有按钮的onTap回调都正确绑定上下文,确保鸿蒙设备上点击事件正常触发
5.3 深色模式适配
所有文本、图标颜色都使用主题色,不使用硬编码颜色,切换深色 / 浅色模式时自动适配
页面背景色使用Scaffold默认背景色,自动跟随主题变化,无颜色断层
弹窗、ListTile 等组件都使用原生样式,自动适配深色模式
5.4 权限说明
所有功能均为纯 UI 实现和本地存储,无需申请任何开源鸿蒙系统权限,直接接入即可使用,无需修改鸿蒙配置文件。
六、开源鸿蒙虚拟机运行验证
6.1 一键运行命令
# 进入鸿蒙工程目录
cd ohos
# 构建HAP安装包
hvigorw assembleHap -p product=default -p buildMode=debug
# 安装到鸿蒙虚拟机
hdc install -r entry/build/default/outputs/default/entry-default-unsigned.hap
# 启动应用
hdc shell aa start -a EntryAbility -b com.example.demo1
Flutter 开源鸿蒙关于页面 - 虚拟机全屏运行验证
效果:应用在开源鸿蒙虚拟机全屏稳定运行,所有页面跳转正常,功能无异常,无闪退、无卡顿、无编译错误
七、新手学习总结
作为刚学 Flutter 和鸿蒙开发的大一新生,这次关于页面完善的开发真的让我收获满满!从最开始的长文本排版混乱、跳转逻辑重复,到最终完成完整的关于页面、三大协议页面、检查更新功能,整个过程让我对 Flutter 的页面布局、长文本渲染、路由跳转有了更深入的理解,而且完全兼容开源鸿蒙平台,成就感直接拉满🥰
这次开发也让我明白了几个新手一定要注意的点:
1.长文本页面一定要用SingleChildScrollView包裹,不然超出屏幕的内容无法滚动,用户根本看不完
2.重复的跳转逻辑一定要抽成统一的方法和独立页面,不然代码冗余,还容易出现两个入口跳转不一致的问题
3.协议类的长文本一定要设置合适的行高,不然文字挤在一起,用户根本不想看,体验很差
4.弹窗一定要正确绑定上下文,不然很容易出现点击没反应、弹窗不弹出的问题
5.隐私政策、用户协议这些内容,一定要写的规范、清晰,符合法律法规,不能随便写几句应付
后续我还会继续优化关于页面,比如添加用户反馈功能、版本更新日志页面、应用评分跳转,也会持续给大家分享我的鸿蒙 Flutter 新手实战内容,和大家一起在开源鸿蒙的生态里慢慢进步✨
如果这篇文章有帮到你,或者你也有更好的关于页面实现思路,欢迎在评论区和我交流呀!
更多推荐

所有评论(0)