【Flutter for OpenHarmony】App引导页与首次使用体验的鸿蒙化适配与实战指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、为什么引导页如此重要?

我是 IntMainJhy,上海某高校大一计算机专业的学生。说起引导页(Onboarding),我一开始觉得这就是个"可有可无"的页面。

我的第一版 App 完全没有引导页,用户打开 App 直接就进入主界面了。室友测试后说:“你这 App 一打开就各种功能,用户根本不知道该从哪里开始。”

后来我才明白,引导页的作用不只是"漂亮",更重要的是:

  1. 引导用户快速上手 - 让用户知道 App 能做什么
  2. 展示核心功能 - 把最重要的功能展示出来
  3. 建立品牌认知 - 第一印象很重要
  4. 收集用户偏好 - 比如是否启用通知

二、引导页设计方案

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 直接进入主页

十、大一学生真实学习总结

引导页让我学到了用户体验设计的重要性。

最重要的几点:

  1. 第一眼很重要

    • 引导页是用户的第一印象
    • 要展示核心功能,不要堆砌功能
  2. 动画增加体验

    • 页面切换要有动画
    • 内容要有入场动画
  3. 状态管理

    • 使用 SharedPreferences 保存状态
    • 区分首次使用和普通使用
  4. 简单清晰

    • 每个页面只讲一个功能
    • 不要在引导页放太多内容

作者:IntMainJhy
创作时间:2026年5月
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐