📑 目录

  1. 📖 概述
  2. 📦 引入三方库步骤
  3. 🎨 具体页面实现功能
  4. ⚠️ 常见错误及解决方案
  5. 🔧 进阶功能
  6. 📚 完整代码示例

📖 概述

本教程将详细介绍如何在 Flutter 应用中集成 intro_slider 三方库,实现一个美观的应用引导页,支持左右滑动查看功能介绍,并提供"跳过"按钮快速进入主界面。

✨ 功能特性

引导页功能

页面导航

左右滑动

上一步按钮

下一步按钮

页面指示器

自定义配置

内容自定义

颜色主题

背景图片

按钮样式

用户体验

跳过功能

首次使用检测

平滑动画

状态保存

详细特性:

  • ✅ 支持左右滑动查看多个引导页
  • 🎨 可自定义每个引导页的内容、颜色、图片
  • ⏭️ 提供"跳过"按钮,快速进入主界面
  • 🔄 支持"上一步"和"下一步"导航按钮
  • 📍 页面指示器显示当前位置
  • 💾 首次使用检测,只在首次启动时显示引导页
  • 🎯 平滑的页面过渡动画

📋 前置要求

  • Flutter SDK 3.9.2 或更高版本
  • Dart SDK 3.9.2 或更高版本
  • 基本的 Flutter 开发知识

📦 引入三方库步骤

📝 步骤 1:添加依赖

pubspec.yaml 文件的 dependencies 部分添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  
  # intro_slider: 引导页滑动组件库
  intro_slider: ^3.1.0
  
  # shared_preferences: 本地数据存储库,用于保存首次使用标志
  shared_preferences: ^2.2.2

📝 代码解读:

  • intro_slider: ^3.1.0:引导页滑动组件库,提供左右滑动、页面指示器、导航按钮等功能
  • shared_preferences: ^2.2.2:本地键值存储库,用于保存用户是否首次使用应用的标志

💡 版本说明:

  • ^3.1.0 表示使用 3.1.0 或更高版本(但低于 4.0.0)
  • 使用 ^ 前缀可以自动获取兼容的更新版本

📁 步骤 2:配置资源文件

pubspec.yamlflutter 部分添加资源路径(如果还没有添加):

flutter:
  uses-material-design: true
  
  assets:
    - assets/images/
    - assets/animations/

📝 代码解读:

  • assets/images/:存放引导页图片资源
  • 如果图片路径不存在,组件会显示占位符,不会导致应用崩溃

📂 步骤 3:创建资源文件夹(可选)

在项目根目录创建以下文件夹结构(如果还没有):

项目根目录/
├── assets/
│   ├── images/
│   │   ├── logo.png              # 应用 Logo
│   │   ├── intro_1.png           # 引导页第1页图片(可选)
│   │   ├── intro_2.png           # 引导页第2页图片(可选)
│   │   ├── intro_3.png           # 引导页第3页图片(可选)
│   │   └── intro_4.png           # 引导页第4页图片(可选)
│   └── animations/               # 动画资源(可选)

💡 提示:

  • 图片资源是可选的,如果不提供图片,组件会正常显示文字内容
  • 建议图片尺寸:1080x1920 或 750x1334(适配不同屏幕)
  • 图片格式:PNG(支持透明)或 JPG

⬇️ 步骤 4:安装依赖

在终端执行以下命令:

flutter pub get

📝 命令说明:

  • flutter pub get:下载并安装 pubspec.yaml 中声明的所有依赖包
  • 执行成功后,依赖包会被下载到项目的 .dart_tool 目录

✅ 验证安装:

安装成功后,可以在 pubspec.lock 文件中看到依赖包的版本信息。


🎨 具体页面实现功能

1️⃣ 创建首选项管理工具类

创建文件 lib/utils/preferences_helper.dart

// 导入 shared_preferences 三方库,用于本地数据存储
import 'package:shared_preferences/shared_preferences.dart';
// 导入 Flutter 基础库,用于 debugPrint
import 'package:flutter/foundation.dart';

/// 首选项管理工具类
/// 用于管理应用的本地存储数据,如首次使用标志等
class PreferencesHelper {
  /// 首次使用标志的键名
  /// 用于在 SharedPreferences 中存储和读取是否首次使用应用的标志
  static const String _keyIsFirstLaunch = 'is_first_launch';

  /// 检查是否首次启动应用
  /// 
  /// 返回值:
  /// - true: 首次启动,需要显示引导页
  /// - false: 非首次启动,直接进入主页面
  static Future<bool> isFirstLaunch() async {
    try {
      // 获取 SharedPreferences 实例
      final prefs = await SharedPreferences.getInstance();
      
      // 读取首次启动标志
      // getBool: 获取布尔值,如果键不存在则返回 null
      // ?? true: 如果返回 null(即键不存在),则默认为 true(首次启动)
      final isFirst = prefs.getBool(_keyIsFirstLaunch) ?? true;
      
      return isFirst;
    } catch (e) {
      // 如果读取失败,默认返回 true,确保用户能看到引导页
      debugPrint('读取首次启动标志失败: $e');
      return true;
    }
  }

  /// 设置已非首次启动
  /// 
  /// 当用户完成引导页或点击跳过时调用此方法
  /// 将首次启动标志设置为 false,下次启动时不再显示引导页
  static Future<void> setNotFirstLaunch() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      // 设置首次启动标志为 false
      await prefs.setBool(_keyIsFirstLaunch, false);
    } catch (e) {
      debugPrint('保存首次启动标志失败: $e');
    }
  }

  /// 重置首次启动标志(用于测试或重新显示引导页)
  static Future<void> resetFirstLaunch() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setBool(_keyIsFirstLaunch, true);
    } catch (e) {
      debugPrint('重置首次启动标志失败: $e');
    }
  }
}

📝 代码解读:

SharedPreferences 是什么?

  • Flutter 提供的本地键值存储工具
  • 数据会持久化保存在设备上,应用重启后仍然存在
  • 适合存储简单的配置信息,如用户偏好设置、首次使用标志等

isFirstLaunch() 方法:

存在

不存在 null

true

false

调用 isFirstLaunch

获取 SharedPreferences 实例

读取键值
getBool _keyIsFirstLaunch

键是否存在?

返回存储的布尔值

使用空值合并运算符
?? true

返回 true
首次启动

返回值

显示引导页

跳过引导页

详细说明:

  • getBool(_keyIsFirstLaunch):读取布尔值
    • 如果键存在,返回对应的布尔值
    • 如果键不存在,返回 null
  • ?? true:空值合并运算符
    • 如果左侧为 null,返回右侧的值(true
    • 首次安装应用时,键不存在,返回 true(需要显示引导页)

setNotFirstLaunch() 方法:

  • setBool(_keyIsFirstLaunch, false):保存布尔值到本地存储
  • 调用此方法后,下次启动应用时 isFirstLaunch() 会返回 false

💡 实际应用场景:

true

false

应用启动

调用 isFirstLaunch

返回结果

显示引导页

显示主页面

用户完成引导

调用 setNotFirstLaunch

保存标志为 false

代码示例:

// 检查是否首次启动
bool isFirst = await PreferencesHelper.isFirstLaunch();
if (isFirst) {
  // 显示引导页
  Navigator.push(context, MaterialPageRoute(builder: (_) => IntroScreen()));
} else {
  // 直接进入主页面
  Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage()));
}

// 用户完成引导后,标记已非首次启动
await PreferencesHelper.setNotFirstLaunch();

2️⃣ 创建引导页组件

创建文件 lib/screens/intro_screen.dart

// 导入 Flutter Material 设计库,提供 UI 组件和主题
import 'package:flutter/material.dart';
// 导入 intro_slider 三方库,用于实现引导页滑动功能
import 'package:intro_slider/intro_slider.dart';
// 导入 intro_slider 的内容配置类
import 'package:intro_slider/slide_object.dart';
// 导入 intro_slider 的配置类
import 'package:intro_slider/scrollbar_behavior_enum.dart';

/// 引导页组件
/// 用于首次启动应用时展示功能介绍
/// 支持左右滑动查看多个引导页,提供"跳过"按钮快速进入主界面
class IntroScreen extends StatefulWidget {
  /// 引导页完成后的回调函数
  /// 当用户完成引导或点击跳过时调用,用于跳转到主页面
  final VoidCallback onDone;

  /// 构造函数
  const IntroScreen({
    super.key,
    required this.onDone,
  });

  
  State<IntroScreen> createState() => _IntroScreenState();
}

/// 引导页状态类
/// 管理引导页的配置和交互逻辑
class _IntroScreenState extends State<IntroScreen> {
  /// 引导页列表
  /// 每个 Slide 对象代表一个引导页
  List<Slide> slides = [];

  /// Widget 初始化方法
  
  void initState() {
    super.initState();
    // 初始化引导页内容
    _initSlides();
  }

  /// 初始化引导页内容
  /// 创建多个引导页,展示天气预报应用的主要功能
  void _initSlides() {
    slides = [
      // 第一页:欢迎页
      Slide(
        title: "欢迎使用天气预报",
        description: "实时掌握天气变化,为您的生活出行提供精准的天气信息",
        pathImage: "assets/images/intro_1.png",
        backgroundColor: const Color(0xFF64B5F6), // 浅蓝色
        titleColor: Colors.white,
        descriptionColor: Colors.white,
        directionColorBegin: Alignment.topLeft,
        directionColorEnd: Alignment.bottomRight,
      ),
      // 第二页:实时天气功能
      Slide(
        title: "实时天气查询",
        description: "获取当前位置的实时天气状况,包括温度、湿度、风速等详细信息",
        pathImage: "assets/images/intro_2.png",
        backgroundColor: const Color(0xFF42A5F5), // 蓝色
        titleColor: Colors.white,
        descriptionColor: Colors.white,
        directionColorBegin: Alignment.topLeft,
        directionColorEnd: Alignment.bottomRight,
      ),
      // 第三页:多城市管理
      Slide(
        title: "多城市管理",
        description: "添加多个城市,快速切换查看不同地区的天气情况,方便出行规划",
        pathImage: "assets/images/intro_3.png",
        backgroundColor: const Color(0xFF1E88E5), // 深蓝色
        titleColor: Colors.white,
        descriptionColor: Colors.white,
        directionColorBegin: Alignment.topLeft,
        directionColorEnd: Alignment.bottomRight,
      ),
      // 第四页:天气预报
      Slide(
        title: "未来天气预报",
        description: "查看未来7天的天气预报,提前了解天气变化趋势,做好出行准备",
        pathImage: "assets/images/intro_4.png",
        backgroundColor: const Color(0xFF1565C0), // 更深蓝色
        titleColor: Colors.white,
        descriptionColor: Colors.white,
        directionColorBegin: Alignment.topLeft,
        directionColorEnd: Alignment.bottomRight,
      ),
    ];
  }

  /// 构建 Widget 树
  
  Widget build(BuildContext context) {
    return IntroSlider(
      // slides: 引导页列表
      slides: slides,
      // onDonePress: 完成按钮点击回调
      onDonePress: () {
        widget.onDone();
      },
      // onSkipPress: 跳过按钮点击回调
      onSkipPress: () {
        widget.onDone();
      },
      // showSkipBtn: 是否显示跳过按钮
      showSkipBtn: true,
      // 按钮样式配置
      skipButtonStyle: ButtonStyle(
        backgroundColor: WidgetStateProperty.all(Colors.white.withOpacity(0.3)),
        foregroundColor: WidgetStateProperty.all(Colors.white),
        padding: WidgetStateProperty.all(
          const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
        ),
        shape: WidgetStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20),
          ),
        ),
      ),
      // 页面指示器配置
      indicatorConfig: IndicatorConfig(
        sizeIndicator: 10,
        colorIndicator: Colors.white,
        colorActiveIndicator: Colors.white.withOpacity(0.5),
        typeIndicatorAnimation: TypeIndicatorAnimation.sizeTransition,
      ),
      // 其他配置
      scrollbarBehavior: ScrollbarBehavior.HIDE,
      hideStatusBar: false,
      backgroundColorAllSlides: Colors.blue,
      isShowPrevBtn: true,
      isShowNextBtn: true,
      isShowDoneBtn: true,
    );
  }
}

image-20260124214039345

📝 代码解读:

Slide 对象属性:

  • title:页面标题文字
  • description:页面描述文字
  • pathImage:页面背景图片路径(可选)
  • backgroundColor:页面背景颜色
  • titleColor:标题文字颜色
  • descriptionColor:描述文字颜色
  • directionColorBegin/End:渐变方向(如果使用渐变背景)

IntroSlider 主要配置:

IntroSlider 组件

slides: 引导页列表

onDonePress: 完成回调

onSkipPress: 跳过回调

showSkipBtn: 显示跳过按钮

indicatorConfig: 指示器配置

按钮显示控制

Slide 1

Slide 2

Slide 3

Slide 4

sizeIndicator: 大小

colorIndicator: 颜色

typeIndicatorAnimation: 动画类型

isShowPrevBtn: 上一步

isShowNextBtn: 下一步

isShowDoneBtn: 完成

配置说明:

  • slides:引导页列表
  • onDonePress:完成按钮回调(最后一页的"开始使用"按钮)
  • onSkipPress:跳过按钮回调(右上角的"跳过"按钮)
  • showSkipBtn:是否显示跳过按钮
  • indicatorConfig:页面指示器配置(小圆点)
  • isShowPrevBtn:是否显示"上一步"按钮
  • isShowNextBtn:是否显示"下一步"按钮
  • isShowDoneBtn:是否显示"完成"按钮(最后一页)

引导页交互流程:

点击跳过

滑动到最后一页

点击上一步

点击下一步

用户进入引导页

用户操作

触发 onSkipPress

显示完成按钮

返回上一页

进入下一页

点击开始使用

触发 onDonePress

调用回调函数

保存首次使用标志

跳转到主页面

💡 自定义按钮样式:

// 自定义跳过按钮
renderSkipBtn: () {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
    decoration: BoxDecoration(
      color: Colors.white.withOpacity(0.3),
      borderRadius: BorderRadius.circular(20),
    ),
    child: const Text(
      "跳过",
      style: TextStyle(
        color: Colors.white,
        fontSize: 16,
        fontWeight: FontWeight.w600,
      ),
    ),
  );
},

3️⃣ 修改主应用入口

修改文件 lib/main.dart

// 导入必要的库
import 'package:flutter/material.dart';
import 'package:flutter_splash_screen/flutter_splash_screen.dart';
import 'screens/splash_screen.dart';
import 'screens/intro_screen.dart';
import 'utils/preferences_helper.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '天气预报',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const SplashWrapper(),
      debugShowCheckedModeBanner: false,
    );
  }
}

/// 启动页包装器 Widget
/// 负责显示启动页、引导页和主页面的跳转逻辑
class SplashWrapper extends StatefulWidget {
  const SplashWrapper({super.key});

  
  State<SplashWrapper> createState() => _SplashWrapperState();
}

/// 启动页包装器的状态类
class _SplashWrapperState extends State<SplashWrapper> {
  /// 页面显示状态枚举
  enum PageState {
    splash,  // 显示启动页
    intro,   // 显示引导页(首次使用)
    home,    // 显示主页面
  }

  PageState _pageState = PageState.splash;

  
  void initState() {
    super.initState();
    _hideNativeSplash();
  }

  /// 隐藏原生启动屏
  Future<void> _hideNativeSplash() async {
    await Future.delayed(const Duration(milliseconds: 100));
    if (mounted) {
      await FlutterSplashScreen.hide();
    }
  }

  /// 启动页完成回调函数
  /// 检查是否首次使用,决定显示引导页还是主页面
  void _onSplashComplete() async {
    if (!mounted) return;

    // 检查是否首次启动应用
    final isFirst = await PreferencesHelper.isFirstLaunch();

    if (mounted) {
      setState(() {
        if (isFirst) {
          // 首次启动,显示引导页
          _pageState = PageState.intro;
        } else {
          // 非首次启动,直接显示主页面
          _pageState = PageState.home;
        }
      });
    }
  }

  /// 引导页完成回调函数
  /// 标记已非首次启动,并跳转到主页面
  void _onIntroComplete() async {
    // 标记已非首次启动
    await PreferencesHelper.setNotFirstLaunch();

    if (mounted) {
      setState(() {
        _pageState = PageState.home;
      });
    }
  }

  /// 构建 Widget 树
  
  Widget build(BuildContext context) {
    switch (_pageState) {
      case PageState.splash:
        // 显示启动页
        return SplashScreen(
          duration: 2000,
          enableAnimation: true,
          logoPath: 'assets/images/logo.png',
          onComplete: _onSplashComplete,
        );

      case PageState.intro:
        // 显示引导页(首次使用)
        return IntroScreen(
          onDone: _onIntroComplete,
        );

      case PageState.home:
        // 显示主页面
        return const MyHomePage(title: '天气预报');
    }
  }
}

📝 代码解读:

页面流程:

主页面 引导页 PreferencesHelper 启动页 应用启动 主页面 引导页 PreferencesHelper 启动页 应用启动 alt [首次使用 (true)] [非首次使用 (false)] 显示启动页 持续2秒 检查是否首次使用 isFirstLaunch() 返回结果 显示引导页 用户滑动/操作 保存标志 setNotFirstLaunch() 跳转到主页面 直接显示主页面

详细步骤:

  1. 启动页(Splash):应用启动时显示,持续 2 秒
  2. 检查首次使用:启动页完成后,调用 PreferencesHelper.isFirstLaunch() 检查
  3. 引导页(Intro):如果是首次使用,显示引导页
  4. 主页面(Home):如果不是首次使用,或引导页完成后,显示主页面

状态管理:

应用启动

首次使用
isFirstLaunch = true

非首次使用
isFirstLaunch = false

完成引导
setNotFirstLaunch

应用退出

Splash

Intro

Home

显示启动页
持续2秒

显示引导页
用户可滑动查看
可点击跳过

显示主页面
正常使用应用

状态切换机制:

  • 使用 PageState 枚举管理页面状态
  • 通过 setState() 切换页面状态
  • 使用 mounted 检查确保 Widget 仍然存在

💡 流程说明:

是 true

否 false

完成引导

点击跳过

应用启动

显示启动页
持续2秒

检查是否首次使用
PreferencesHelper.isFirstLaunch

显示引导页
IntroScreen

显示主页面
MyHomePage

用户操作

保存首次使用标志
setNotFirstLaunch

标记已非首次启动


⚠️ 常见错误及解决方案

❌ 错误 1:依赖安装失败

错误信息:

Running "flutter pub get" in ffohnotes...
pub get failed

可能原因:

  1. 网络连接问题
  2. pubspec.yaml 语法错误
  3. Flutter SDK 版本不兼容

解决方案:

# 1. 检查网络连接
ping pub.dev

# 2. 清理缓存后重新安装
flutter clean
flutter pub get

# 3. 检查 pubspec.yaml 语法
# 确保缩进正确(使用空格,不要使用 Tab)
# 确保版本号格式正确(如 ^3.1.0)

# 4. 使用国内镜像(如果网络较慢)
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
flutter pub get

❌ 错误 2:图片资源找不到

错误信息:

Unable to load asset: assets/images/intro_1.png

可能原因:

  1. 图片文件不存在
  2. pubspec.yaml 中未配置资源路径
  3. 图片路径拼写错误

解决方案:

// 方案1:检查图片是否存在
// 确保 assets/images/intro_1.png 文件存在

// 方案2:在 pubspec.yaml 中配置资源路径
flutter:
  assets:
    - assets/images/

// 方案3:使用占位符(推荐)
// intro_slider 会自动处理图片加载失败的情况
// 如果图片不存在,会显示占位符,不会导致应用崩溃

// 方案4:不提供图片路径(可选)
Slide(
  title: "欢迎使用天气预报",
  description: "实时掌握天气变化...",
  // pathImage: "assets/images/intro_1.png", // 注释掉或删除
  backgroundColor: const Color(0xFF64B5F6),
  // ...
)

❌ 错误 3:SharedPreferences 初始化失败

错误信息:

PlatformException(channel-error, Unable to establish connection on channel., null, null)

可能原因:

  1. 平台特定配置缺失(Android/iOS)
  2. 权限问题

解决方案:

Android 配置(android/app/src/main/AndroidManifest.xml):

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 添加存储权限(如果需要) -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
    <application
        android:label="天气预报"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <!-- ... -->
    </application>
</manifest>

iOS 配置(通常不需要额外配置):

  • shared_preferences 在 iOS 上使用 UserDefaults,通常不需要额外权限

错误处理:

static Future<bool> isFirstLaunch() async {
  try {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool(_keyIsFirstLaunch) ?? true;
  } catch (e) {
    // 如果初始化失败,返回 true(显示引导页)
    debugPrint('SharedPreferences 初始化失败: $e');
    return true;
  }
}

❌ 错误 4:引导页不显示

错误信息:

  • 应用启动后直接进入主页面,没有显示引导页

问题诊断流程:

返回 false

返回 true

未导入

已导入

setState 未调用

状态切换错误

引导页不显示

检查 isFirstLaunch 返回值

调用 resetFirstLaunch
重置标志

检查组件集成

添加 import 语句

检查状态管理

确保调用 setState

检查 PageState 枚举

重新运行应用

引导页是否显示?

问题解决

查看调试日志

检查回调函数

可能原因:

  1. PreferencesHelper.isFirstLaunch() 返回 false
  2. 引导页组件未正确集成
  3. 状态管理问题

解决方案:

// 1. 检查首次使用标志
// 在调试时,可以重置标志来测试引导页
await PreferencesHelper.resetFirstLaunch();

// 2. 添加调试日志
void _onSplashComplete() async {
  if (!mounted) return;
  
  final isFirst = await PreferencesHelper.isFirstLaunch();
  debugPrint('是否首次启动: $isFirst'); // 添加日志
  
  if (mounted) {
    setState(() {
      if (isFirst) {
        _pageState = PageState.intro;
        debugPrint('切换到引导页'); // 添加日志
      } else {
        _pageState = PageState.home;
        debugPrint('切换到主页面'); // 添加日志
      }
    });
  }
}

// 3. 检查引导页组件是否正确导入
import 'screens/intro_screen.dart';

// 4. 确保 IntroScreen 组件正确创建
case PageState.intro:
  return IntroScreen(
    onDone: _onIntroComplete, // 确保回调函数正确传递
  );

❌ 错误 5:按钮点击无响应

错误信息:

  • 点击"跳过"或"开始使用"按钮没有反应

问题排查流程:

未传递

已传递

未实现

已实现

未调用 widget.onDone

已调用

按钮点击无响应

检查回调函数传递

添加 onDone 参数

检查回调函数实现

实现 _onIntroComplete

检查 IntroScreen 内部调用

确保调用 widget.onDone

添加调试日志

查看控制台输出

日志是否打印?

回调函数正常执行

检查 Widget 生命周期

可能原因:

  1. 回调函数未正确传递
  2. onDone 回调函数为空或未实现

解决方案:

// 1. 检查回调函数是否正确传递
IntroScreen(
  onDone: _onIntroComplete, // 确保传递了回调函数
)

// 2. 确保回调函数已实现
void _onIntroComplete() async {
  debugPrint('引导页完成回调被调用'); // 添加日志验证
  await PreferencesHelper.setNotFirstLaunch();
  
  if (mounted) {
    setState(() {
      _pageState = PageState.home;
    });
  }
}

// 3. 检查 IntroScreen 内部是否正确调用回调
onDonePress: () {
  widget.onDone(); // 确保调用了外部传入的回调
},
onSkipPress: () {
  widget.onDone(); // 确保调用了外部传入的回调
},

❌ 错误 6:页面指示器不显示

错误信息:

  • 引导页底部没有显示页面指示器(小圆点)

可能原因:

  1. indicatorConfig 配置错误
  2. 颜色设置与背景色相同,导致看不见

解决方案:

// 1. 检查指示器配置
indicatorConfig: IndicatorConfig(
  sizeIndicator: 10, // 确保大小不为 0
  colorIndicator: Colors.white, // 确保颜色与背景色不同
  colorActiveIndicator: Colors.white.withOpacity(0.5),
  typeIndicatorAnimation: TypeIndicatorAnimation.sizeTransition,
),

// 2. 如果背景是白色,使用深色指示器
indicatorConfig: IndicatorConfig(
  sizeIndicator: 10,
  colorIndicator: Colors.blue, // 使用深色
  colorActiveIndicator: Colors.blue.withOpacity(0.5),
  typeIndicatorAnimation: TypeIndicatorAnimation.sizeTransition,
),

❌ 错误 7:滑动不流畅

错误信息:

  • 左右滑动引导页时卡顿或不流畅

可能原因:

  1. 图片过大
  2. 动画效果过多
  3. 设备性能问题

解决方案:

// 1. 优化图片大小
// 建议图片尺寸:1080x1920 或 750x1334
// 使用图片压缩工具减小文件大小

// 2. 减少动画效果
indicatorConfig: IndicatorConfig(
  typeIndicatorAnimation: TypeIndicatorAnimation.none, // 禁用动画
),

// 3. 使用占位符代替大图片
// 如果图片加载慢,可以先显示占位符
Slide(
  title: "欢迎使用天气预报",
  description: "实时掌握天气变化...",
  // pathImage: "assets/images/intro_1.png", // 不提供图片
  backgroundColor: const Color(0xFF64B5F6),
  // ...
)

🔧 进阶功能

1️⃣ 自定义引导页样式

自定义按钮样式:

IntroSlider(
  slides: slides,
  // 自定义跳过按钮
  renderSkipBtn: () {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
      decoration: BoxDecoration(
        color: Colors.white.withOpacity(0.3),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(color: Colors.white, width: 2),
      ),
      child: const Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.close, color: Colors.white, size: 18),
          SizedBox(width: 4),
          Text(
            "跳过",
            style: TextStyle(
              color: Colors.white,
              fontSize: 16,
              fontWeight: FontWeight.w600,
            ),
          ),
        ],
      ),
    );
  },
  // 自定义完成按钮
  renderDoneBtn: () {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 12),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.white, Colors.blue.shade100],
        ),
        borderRadius: BorderRadius.circular(25),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.2),
            blurRadius: 8,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: const Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            "开始使用",
            style: TextStyle(
              color: Color(0xFF1565C0),
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(width: 8),
          Icon(Icons.arrow_forward, color: Color(0xFF1565C0), size: 20),
        ],
      ),
    );
  },
)

自定义页面布局:

// 使用自定义 Widget 构建页面内容
Slide(
  title: "欢迎使用天气预报",
  description: "实时掌握天气变化...",
  // 使用自定义 Widget
  widgetDescription: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Icon(Icons.wb_sunny, size: 100, color: Colors.white),
      const SizedBox(height: 20),
      const Text(
        "欢迎使用天气预报",
        style: TextStyle(
          fontSize: 28,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      ),
      const SizedBox(height: 16),
      const Text(
        "实时掌握天气变化,为您的生活出行提供精准的天气信息",
        textAlign: TextAlign.center,
        style: TextStyle(
          fontSize: 16,
          color: Colors.white,
        ),
      ),
    ],
  ),
  backgroundColor: const Color(0xFF64B5F6),
)

2️⃣ 添加页面切换动画

IntroSlider(
  slides: slides,
  // 页面切换动画配置
  animationDuration: const Duration(milliseconds: 300),
  // 滑动方向(水平或垂直)
  verticalScrollbarBehavior: ScrollbarBehavior.HIDE,
)

3️⃣ 根据设备语言显示不同内容

void _initSlides() {
  // 获取设备语言
  final locale = Localizations.localeOf(context);
  final isChinese = locale.languageCode == 'zh';

  slides = [
    Slide(
      title: isChinese ? "欢迎使用天气预报" : "Welcome to Weather App",
      description: isChinese 
        ? "实时掌握天气变化,为您的生活出行提供精准的天气信息"
        : "Get real-time weather updates for your daily life and travel",
      backgroundColor: const Color(0xFF64B5F6),
      titleColor: Colors.white,
      descriptionColor: Colors.white,
    ),
    // ... 其他页面
  ];
}

4️⃣ 添加页面切换监听

IntroSlider(
  slides: slides,
  // 页面切换回调
  onTabChangeCompleted: (index) {
    debugPrint('切换到第 ${index + 1} 页');
    // 可以在这里添加页面切换动画、统计等
  },
)

5️⃣ 条件显示引导页

版本更新检测流程:

不同

相同

检查是否显示引导页

获取上次引导页版本
last_intro_version

获取当前应用版本
currentVersion

版本是否相同?

版本已更新

检查首次启动标志

保存当前版本号

返回 true
显示引导页

是否首次启动?

返回 false
不显示引导页

代码实现:

// 根据应用版本决定是否显示引导页
Future<bool> shouldShowIntro() async {
  final prefs = await SharedPreferences.getInstance();
  final lastVersion = prefs.getString('last_intro_version');
  final currentVersion = '1.0.0'; // 从 package_info_plus 获取
  
  // 如果版本更新,重新显示引导页
  if (lastVersion != currentVersion) {
    await prefs.setString('last_intro_version', currentVersion);
    return true;
  }
  
  // 检查是否首次启动
  return prefs.getBool('is_first_launch') ?? true;
}

📚 完整代码示例

文件结构

lib/

main.dart
应用入口

screens/

utils/

splash_screen.dart
启动页

intro_screen.dart
引导页

preferences_helper.dart
首选项管理工具

SplashWrapper
页面状态管理

MyHomePage
主页面

目录说明:

lib/
├── main.dart                    # 应用入口
├── screens/
│   ├── splash_screen.dart       # 启动页
│   └── intro_screen.dart        # 引导页
└── utils/
    └── preferences_helper.dart  # 首选项管理工具

完整代码

所有代码文件已在前面章节提供,这里不再重复。完整的项目代码包括:

  1. lib/utils/preferences_helper.dart:首选项管理工具类
  2. lib/screens/intro_screen.dart:引导页组件
  3. lib/main.dart:主应用入口(已修改)

运行项目

# 1. 安装依赖
flutter pub get

# 2. 运行应用
flutter run

# 3. 测试引导页
# 首次运行会显示引导页
# 完成引导后,再次运行不会显示引导页

# 4. 重置引导页(用于测试)
# 在代码中调用:
await PreferencesHelper.resetFirstLaunch();

📖 总结

本教程详细介绍了如何使用 intro_slider 三方库实现 Flutter 应用引导页,包括:

引入三方库步骤:添加依赖、配置资源、安装依赖
具体页面实现:创建工具类、引导页组件、集成到主应用
常见错误解决:依赖安装、资源加载、状态管理等问题
进阶功能:自定义样式、多语言支持、条件显示等

🎯 关键要点

  1. 首次使用检测:使用 SharedPreferences 保存首次使用标志
  2. 页面状态管理:使用枚举管理启动页、引导页、主页面的切换
  3. 用户体验:提供"跳过"按钮,允许用户快速进入主界面
  4. 错误处理:添加异常处理,确保应用稳定运行

📚 相关资源


🎉 祝你开发顺利! 🚀

欢迎加入开源鸿蒙跨平台社区

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐