【Flutter for OpenHarmony】第三方库 App引导页与首次使用体验的鸿蒙化适配与实战指南
·
【Flutter for OpenHarmony】App引导页与首次使用体验的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、为什么引导页如此重要?
我是 IntMainJhy,上海某高校大一计算机专业的学生。说起引导页(Onboarding),我一开始觉得这就是个"可有可无"的页面。
我的第一版 App 完全没有引导页,用户打开 App 直接就进入主界面了。室友测试后说:“你这 App 一打开就各种功能,用户根本不知道该从哪里开始。”
后来我才明白,引导页的作用不只是"漂亮",更重要的是:
- 引导用户快速上手 - 让用户知道 App 能做什么
- 展示核心功能 - 把最重要的功能展示出来
- 建立品牌认知 - 第一印象很重要
- 收集用户偏好 - 比如是否启用通知
二、引导页设计方案
2.1 引导页内容规划
心理健康 App 的引导页,我设计了4个页面:
| 页面 | 标题 | 内容 | 颜色 |
|---|---|---|---|
| 1 | 记录心情 | 记录每天的情绪变化 | 紫色 |
| 2 | 冥想放松 | 专业冥想指导音频 | 蓝色 |
| 3 | 心理测试 | 科学心理健康评估 | 橙色 |
| 4 | 开始使用 | 欢迎来到心理健康App | 绿色 |
2.2 引导页结构
┌─────────────────────────────────────┐
│ │
│ [图片/图标] │
│ │
│ [标题文字] │
│ │
│ [描述文字] │
│ │
│ │
│ │
│ │
├─────────────────────────────────────┤
│ ● ● ○ ○ │ ← 指示器
├─────────────────────────────────────┤
│ [跳过] [下一步] │ ← 导航按钮
└─────────────────────────────────────┘
三、flutter_onboarding 介绍
3.1 什么是 flutter_onboarding?
flutter_onboarding 是一个专门用于创建引导页的库:
# pubspec.yaml
dependencies:
smooth_page_indicator: ^1.2.0+3
3.2 核心功能
| 功能 | 说明 |
|---|---|
| 页面指示器 | 显示当前位置 |
| 页面切换动画 | 平滑的滑动效果 |
| 按钮控制 | 跳过/下一步/完成 |
四、引导页实现
4.1 引导页数据模型
// lib/mental_health/models/onboarding_model.dart
import 'package:flutter/material.dart';
/// 引导页数据模型
class OnboardingPage {
final String title;
final String description;
final IconData icon;
final Color backgroundColor;
final Color textColor;
const OnboardingPage({
required this.title,
required this.description,
required this.icon,
required this.backgroundColor,
required this.textColor,
});
/// 引导页数据
static const List<OnboardingPage> pages = [
OnboardingPage(
title: '记录心情',
description: '每天记录你的情绪变化,\n了解自己的内心世界',
icon: Icons.mood,
backgroundColor: Color(0xFF6C63FF),
textColor: Colors.white,
),
OnboardingPage(
title: '冥想放松',
description: '专业冥想指导音频,\n帮助你放松身心、减轻压力',
icon: Icons.self_improvement,
backgroundColor: Color(0xFF3498DB),
textColor: Colors.white,
),
OnboardingPage(
title: '心理测试',
description: '科学的心理健康评估,\nPHQ-9和GAD-7量表',
icon: Icons.psychology,
backgroundColor: Color(0xFFF39C12),
textColor: Colors.white,
),
OnboardingPage(
title: '呼吸训练',
description: '多种呼吸练习模式,\n帮助你平复情绪、恢复平静',
icon: Icons.air,
backgroundColor: Color(0xFF27AE60),
textColor: Colors.white,
),
];
}
4.2 引导页组件
// lib/mental_health/widgets/onboarding_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import '../models/onboarding_model.dart';
/// 引导页组件
class OnboardingScreen extends StatefulWidget {
final VoidCallback onComplete;
const OnboardingScreen({
super.key,
required this.onComplete,
});
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final PageController _pageController = PageController();
int _currentPage = 0;
bool _isLastPage = false;
void dispose() {
_pageController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// 页面内容
PageView.builder(
controller: _pageController,
itemCount: OnboardingPage.pages.length,
onPageChanged: (index) {
setState(() {
_currentPage = index;
_isLastPage = index == OnboardingPage.pages.length - 1;
});
},
itemBuilder: (context, index) {
return _OnboardingPageContent(page: OnboardingPage.pages[index]);
},
),
// 底部导航
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.1),
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 跳过按钮
TextButton(
onPressed: () {
_pageController.animateToPage(
OnboardingPage.pages.length - 1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
child: Text(
_isLastPage ? '' : '跳过',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
),
// 页面指示器
SmoothPageIndicator(
controller: _pageController,
count: OnboardingPage.pages.length,
effect: WormEffect(
dotWidth: 10,
dotHeight: 10,
spacing: 8,
activeDotColor: OnboardingPage.pages[_currentPage].backgroundColor,
dotColor: Colors.grey[300]!,
),
),
// 下一步/完成按钮
TextButton(
onPressed: () {
if (_isLastPage) {
widget.onComplete();
} else {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
},
child: Text(
_isLastPage ? '开始使用' : '下一步',
style: TextStyle(
color: OnboardingPage.pages[_currentPage].backgroundColor,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
],
),
);
}
}
/// 单个引导页面内容
class _OnboardingPageContent extends StatelessWidget {
final OnboardingPage page;
const _OnboardingPageContent({required this.page});
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
page.backgroundColor.withOpacity(0.8),
page.backgroundColor,
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
// 图标
Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
page.icon,
size: 80,
color: Colors.white,
),
)
.animate()
.scale(
begin: const Offset(0.5, 0.5),
end: const Offset(1.0, 1.0),
duration: 600.ms,
curve: Curves.easeOutBack,
)
.fadeIn(duration: 400.ms),
const SizedBox(height: 48),
// 标题
Text(
page.title,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: page.textColor,
),
textAlign: TextAlign.center,
)
.animate()
.fadeIn(delay: 200.ms, duration: 400.ms)
.slideY(begin: 0.3, end: 0),
const SizedBox(height: 16),
// 描述
Text(
page.description,
style: TextStyle(
fontSize: 18,
color: page.textColor.withOpacity(0.9),
height: 1.5,
),
textAlign: TextAlign.center,
)
.animate()
.fadeIn(delay: 400.ms, duration: 400.ms)
.slideY(begin: 0.3, end: 0),
const Spacer(),
const Spacer(),
],
),
),
),
);
}
}
4.3 自定义引导页(不使用第三方库)
/// 自定义引导页(不依赖第三方库)
class CustomOnboardingScreen extends StatefulWidget {
final VoidCallback onComplete;
const CustomOnboardingScreen({
super.key,
required this.onComplete,
});
State<CustomOnboardingScreen> createState() => _CustomOnboardingScreenState();
}
class _CustomOnboardingScreenState extends State<CustomOnboardingScreen> {
final PageController _pageController = PageController();
int _currentPage = 0;
void dispose() {
_pageController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: OnboardingPage.pages.length,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
itemBuilder: (context, index) {
final page = OnboardingPage.pages[index];
return _CustomPageContent(page: page);
},
),
),
// 底部导航栏
Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 页面指示器
Row(
children: List.generate(
OnboardingPage.pages.length,
(index) => AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.only(right: 8),
width: _currentPage == index ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: _currentPage == index
? OnboardingPage.pages[_currentPage].backgroundColor
: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
),
),
// 按钮
Row(
children: [
if (_currentPage < OnboardingPage.pages.length - 1)
TextButton(
onPressed: widget.onComplete,
child: const Text('跳过'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
if (_currentPage < OnboardingPage.pages.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
widget.onComplete();
}
},
style: ElevatedButton.styleFrom(
backgroundColor:
OnboardingPage.pages[_currentPage].backgroundColor,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
child: Text(
_currentPage < OnboardingPage.pages.length - 1
? '下一步'
: '开始使用',
style: const TextStyle(color: Colors.white),
),
),
],
),
],
),
),
],
),
);
}
}
/// 自定义页面内容
class _CustomPageContent extends StatelessWidget {
final OnboardingPage page;
const _CustomPageContent({required this.page});
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
page.backgroundColor.withOpacity(0.8),
page.backgroundColor,
],
),
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 顶部装饰
const SizedBox(height: 60),
// 图标
Icon(
page.icon,
size: 120,
color: Colors.white,
),
const SizedBox(height: 48),
// 标题
Text(
page.title,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// 描述
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
page.description,
style: TextStyle(
fontSize: 18,
color: Colors.white.withOpacity(0.9),
height: 1.6,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
五、首次使用检测与导航
// lib/mental_health/screens/initial_screen.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../widgets/onboarding_page.dart';
/// 初始屏幕 - 检测是否首次使用
class InitialScreen extends StatefulWidget {
final VoidCallback onOnboardingComplete;
const InitialScreen({
super.key,
required this.onOnboardingComplete,
});
State<InitialScreen> createState() => _InitialScreenState();
}
class _InitialScreenState extends State<InitialScreen> {
bool _isFirstLaunch = true;
bool _isLoading = true;
void initState() {
super.initState();
_checkFirstLaunch();
}
Future<void> _checkFirstLaunch() async {
final prefs = await SharedPreferences.getInstance();
final hasSeenOnboarding = prefs.getBool('has_seen_onboarding') ?? false;
setState(() {
_isFirstLaunch = !hasSeenOnboarding;
_isLoading = false;
});
}
Future<void> _completeOnboarding() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('has_seen_onboarding', true);
widget.onOnboardingComplete();
}
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (_isFirstLaunch) {
return OnboardingScreen(onComplete: _completeOnboarding);
}
// 直接跳转到主页
return const MentalHealthHomeScreen();
}
}
六、在主应用中使用
// main.dart
void main() {
runApp(const MentalHealthApp());
}
class MentalHealthApp extends StatelessWidget {
const MentalHealthApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '心理健康',
theme: ThemeData(
primaryColor: const Color(0xFF6C63FF),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6C63FF),
),
),
home: InitialScreen(
onOnboardingComplete: () {
// 跳转到主页面
runApp(const MainApp());
},
),
);
}
}
/// 主应用
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '心理健康',
theme: ThemeData(
primaryColor: const Color(0xFF6C63FF),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6C63FF),
),
),
home: const MentalHealthHomeScreen(),
);
}
}
七、鸿蒙平台专属适配
适配点:引导页在鸿蒙设备上的显示
问题:鸿蒙设备的屏幕比例可能和 Android 不同。
解决方案:
// 使用 SafeArea 确保内容不被遮挡
SafeArea(
child: Column(
children: [
// 内容自适应
],
),
)
// 使用 LayoutBuilder 响应不同屏幕
LayoutBuilder(
builder: (context, constraints) {
return Container(
height: constraints.maxHeight * 0.6, // 60% 高度
);
},
)
八、我的踩坑记录
坑1:PageController 未正确 dispose
报错现象:页面销毁后控制器还在运行。
原因:PageController 没有在 dispose 中释放。
解决代码:
void dispose() {
_pageController.dispose(); // 必须调用
super.dispose();
}
坑2:指示器不跟随页面变化
问题:滑动页面后,指示器没有更新。
原因:SmoothPageIndicator 没有绑定 PageController。
解决代码:
SmoothPageIndicator(
controller: _pageController, // 必须绑定
count: OnboardingPage.pages.length,
effect: WormEffect(...),
)
坑3:首次使用判断逻辑错误
问题:用户跳过引导页后,再次打开 App 还是会显示引导页。
原因:没有正确保存"已看过引导页"的状态。
解决代码:
Future<void> _completeOnboarding() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('has_seen_onboarding', true); // 保存状态
}
九、功能验证清单
| 序号 | 检查项 | 测试场景 | 预期结果 |
|---|---|---|---|
| 1 | 首次启动 | 全新安装 App | 显示引导页 |
| 2 | 滑动切换 | 左右滑动页面 | 页面切换,指示器更新 |
| 3 | 点击下一步 | 点击下一步按钮 | 跳转到下一页 |
| 4 | 点击跳过 | 点击跳过按钮 | 跳转到最后一页 |
| 5 | 点击完成 | 在最后一页点击开始使用 | 进入主页 |
| 6 | 二次启动 | 再次打开 App | 直接进入主页 |
十、大一学生真实学习总结
引导页让我学到了用户体验设计的重要性。
最重要的几点:
-
第一眼很重要
- 引导页是用户的第一印象
- 要展示核心功能,不要堆砌功能
-
动画增加体验
- 页面切换要有动画
- 内容要有入场动画
-
状态管理
- 使用 SharedPreferences 保存状态
- 区分首次使用和普通使用
-
简单清晰
- 每个页面只讲一个功能
- 不要在引导页放太多内容
作者:IntMainJhy
创作时间:2026年5月
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)