Flutter新用户引导插件onboarding_overlay的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter新用户引导插件onboarding_overlay的使用

介绍

onboarding_overlay 是一个灵活的新用户引导组件,它可以在任意数量的步骤和任意起始点启动和停止。该插件提供了丰富的自定义选项,如设置文本样式、背景颜色、箭头位置等,以及通过 stepBuilder 实现更复杂的内容展示。

示例代码

主要功能实现

1. 创建焦点节点列表

首先需要创建一组 FocusNode,用于标识界面上需要进行引导说明的位置。

final List<FocusNode> overlayKeys = <FocusNode>[
  FocusNode(),
  FocusNode(),
  FocusNode(),
];

2. 定义引导步骤

接下来定义每个引导步骤的具体内容,包括标题、正文、是否显示标签框、是否有箭头指向等属性。

final List<OnboardingStep> steps = [
  OnboardingStep(
    focusNode: _focusNodes != null ? _focusNodes[0] : null,
    title: "Hi",
    bodyText: "Check this out",
    hasLabelBox: false,
    fullscreen: true,
    overlayColor: Theme.of(context).primaryColorDark.withOpacity(0.8),
    hasArrow: false,
  ),
];

3. 将 FocusNode 应用到目标组件上

确保在界面上正确地为各个元素分配了对应的 FocusNode,以便于后续能够准确地触发相应的引导提示。

Focus(
  focusNode: focusNode[0],
  child: Text('You have pushed the button this many times:'),
)

4. 添加 Onboarding 组件到应用中

Onboarding 组件插入到MaterialApp之下,并传入之前准备好的步骤列表和其他配置项。

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  final GlobalKey<OnboardingState> onboardingKey = GlobalKey<OnboardingState>();

  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  List<FocusNode> focusNodes = <FocusNode>[];

  @override
  void initState() {
    super.initState();

    focusNodes = List<FocusNode>.generate(
      7,
      (int i) => FocusNode(debugLabel: i.toString()),
      growable: false,
    );
  }

  @override
  Widget build(BuildContext context) => MaterialApp(
        home: Onboarding(
          key: widget.onboardingKey,
          steps: steps,
          onChanged: (int index) {
            debugPrint('----index $index');
            if (index == 5) {
              /// interrupt onboarding on specific step
              /// widget.onboardingKey.currentState.hide();
              /// or do something else
            }
          },
          child: Home(
            focusNodes: focusNodes,
          ),
        ),
      );
}

5. 显示引导界面

可以在某个特定条件下调用 show() 方法来开始显示引导流程。

final OnboardingState? onboarding = Onboarding.of(context);

if (onboarding != null) {
  onboarding.show();
}

或者在页面初始化时立即显示:

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
    final OnboardingState? onboarding = Onboarding.of(context);
    if (onboarding != null) {
      onboarding.show();
    }
  });
}

高级特性

  • 自定义布局:通过设置 hasLabelBox=truelabelBoxDecoration 可以创建带有装饰效果的标签框,并且可以指定箭头的位置。
  • 点击行为控制:利用 overlayBehavior 参数决定点击事件如何处理(例如忽略、传递给子组件或同时响应)。
  • 部分显示:使用 showWithStepsshowFromIndex 方法只显示某些特定的引导步骤。
  • 动画效果:从版本3.0.0开始支持围绕焦点元素添加脉冲动画。
  • 调试边界:开启 debugBoundaries 参数后会在标签框周围绘制红色边框,方便调试布局问题。

完整示例代码

以下是完整的例子,演示了如何集成并使用 onboarding_overlay 插件:

import 'dart:developer';

import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:onboarding_overlay/onboarding_overlay.dart';

void main() {
  // timeDilation = 2;

  runApp(App());
}

final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

class App extends StatefulWidget {
  final GlobalKey<OnboardingState> onboardingKey = GlobalKey<OnboardingState>();

  App({Key? key}) : super(key: key);

  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  final GlobalKey closeKey = GlobalKey();
  late List<FocusNode> focusNodes;

  @override
  void initState() {
    super.initState();

    focusNodes = List<FocusNode>.generate(
      18,
      (int i) => FocusNode(debugLabel: 'Onboarding Focus Node $i'),
      growable: false,
    );
  }

  @override
  Widget build(BuildContext context) => MaterialApp(
        theme: ThemeData.from(
          colorScheme: ColorScheme.fromSwatch(
            primarySwatch: Colors.blue,
            backgroundColor: Colors.white,
          ),
        ),
        home: Onboarding(
          key: widget.onboardingKey,
          autoSizeTexts: true,
          debugBoundaries: true,
          steps: <OnboardingStep>[
            // ... 其他步骤 ...
          ],
          onChanged: (int index) {
            if (index == 4) {
              // 关闭抽屉或其他逻辑
            }
            final int? currentIndex =
                widget.onboardingKey.currentState?.controller.currentIndex;

            log('currentIndex $currentIndex');
          },
          child: Home(
            focusNodes: focusNodes,
          ),
        ),
      );
}

class Home extends StatefulWidget {
  const Home({
    Key? key,
    required this.focusNodes,
  }) : super(key: key);

  final List<FocusNode> focusNodes;

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late int _counter;

  @override
  void initState() {
    super.initState();
    _counter = 0;
  }

  @override
  void dispose() {
    super.dispose();
  }

  void _increment(BuildContext context) {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        leading: IconButton(
          focusNode: widget.focusNodes[4],
          icon: const Icon(Icons.menu),
          onPressed: () {
            scaffoldKey.currentState?.openDrawer();
          },
        ),
        title: Focus(
          focusNode: widget.focusNodes[3],
          child: const Text('Super Long Title'),
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.mic),
            onPressed: () {},
          ),
          IconButton(
            icon: const Icon(Icons.subscript),
            onPressed: () {},
          ),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () {},
          )
        ],
      ),
      drawer: Drawer(
        child: ListView(
          children: [
            const DrawerHeader(
              child: Text('Menu'),
            ),
            ListTile(
              focusNode: widget.focusNodes[5],
              title: const Text('Close menu'),
              onTap: () {
                Navigator.of(context).pop();
              },
            )
          ],
        ),
      ),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (BuildContext context, int index) {
          return index == 5
              ? Focus(
                  focusNode: widget.focusNodes[13],
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Focus(
                            focusNode: widget.focusNodes[0],
                            child: TextButton(
                              child: const Text(
                                  'You have pushed the button this many times:'),
                              onPressed: () {
                                print('do something');
                              },
                            ),
                          ),
                          Focus(
                            focusNode: widget.focusNodes[6],
                            child: Text(
                              '$_counter',
                              style: Theme.of(context).textTheme.headlineMedium,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                )
              : ListTile(
                  leading: Focus(
                    child: const Icon(Icons.alarm),
                    focusNode: widget.focusNodes[index + 8],
                  ),
                  title: Text('Item ${index + 1}'),
                  trailing: Text('${index + 8}'),
                );
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: Padding(
        padding: const EdgeInsets.only(left: 16, right: 16),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          mainAxisSize: MainAxisSize.max,
          children: [
            FloatingActionButton(
              focusNode: widget.focusNodes[1],
              onPressed: () {
                final OnboardingState? onboarding = Onboarding.of(context);
                if (onboarding != null) {
                  onboarding.show();
                }
              },
              child: const Icon(Icons.add),
            ),
            FloatingActionButton(
              focusNode: widget.focusNodes[2],
              onPressed: () {
                _increment(context);
              },
              child: const Icon(Icons.add),
            ),
          ],
        ),
      ),
    );
  }
}

此代码片段展示了如何在一个简单的Flutter应用程序中使用 onboarding_overlay 来实现新用户引导功能。根据实际需求调整步骤内容和UI布局即可快速上手。


更多关于Flutter新用户引导插件onboarding_overlay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter新用户引导插件onboarding_overlay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,onboarding_overlay 是一个在 Flutter 中用于实现新用户引导功能的插件。它允许你在应用的特定部分显示半透明的覆盖层,并包含一些说明文字或图标,帮助新用户了解应用的功能。

以下是一个简单的代码示例,展示了如何使用 onboarding_overlay 插件来实现新用户引导:

  1. 首先,确保在 pubspec.yaml 文件中添加依赖
dependencies:
  flutter:
    sdk: flutter
  onboarding_overlay: ^1.3.0  # 请检查最新版本号并替换
  1. 运行 flutter pub get 命令来获取依赖包。

  2. 然后,在你的 Dart 文件中使用 OnboardingOverlay 组件

import 'package:flutter/material.dart';
import 'package:onboarding_overlay/onboarding_overlay.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Onboarding Overlay Example'),
        ),
        body: OnboardingOverlay(
          // 创建一个全局的 key 来控制 Overlay
          key: GlobalKey<OnboardingOverlayState>(),
          // 定义引导步骤列表
          steps: [
            OnboardingStep(
              title: 'Welcome!',
              content: 'This is the home screen of our app.',
              position: OnboardingStepPosition.BOTTOM,
              // 可以添加背景图片或者颜色
              // background: Container(color: Colors.blue.withOpacity(0.5)),
              // 定义目标小部件的 key
              targetKey: _homeScreenKey,
            ),
            OnboardingStep(
              title: 'Search',
              content: 'You can search for items here.',
              position: OnboardingStepPosition.TOP,
              targetKey: _searchBarKey,
            ),
            // 可以添加更多步骤...
          ],
          // 控制是否显示 Overlay
          child: MyHomePage(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final GlobalKey _homeScreenKey = GlobalKey();
  final GlobalKey _searchBarKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 为 home screen 部分分配 key
          Container(
            key: _homeScreenKey,
            height: 200,
            color: Colors.grey[200],
            child: Center(child: Text('Home Screen')),
          ),
          SizedBox(height: 24),
          // 为 search bar 部分分配 key
          TextField(
            key: _searchBarKey,
            decoration: InputDecoration(
              labelText: 'Search...',
              border: OutlineInputBorder(),
            ),
          ),
          // 可以添加更多 UI 元素...
        ],
      ),
    );
  }
}

在这个示例中:

  • 我们定义了两个引导步骤,一个是关于首页的说明,另一个是关于搜索栏的说明。
  • OnboardingSteptargetKey 属性用于指定要覆盖的目标小部件的 GlobalKey
  • OnboardingOverlaychild 属性包含了应用的主要界面。
  • 你可以通过调用 OnboardingOverlayState 的方法来控制 Overlay 的显示,例如 showNext()showPrevious()。这可以通过按钮点击事件或其他逻辑来实现,但在这个示例中没有展示。

请注意,onboarding_overlay 插件的 API 可能会随着版本的更新而变化,因此请参考最新的文档和示例代码以确保兼容性。

回到顶部