Flutter跟随引导插件follow_the_leader的使用

Flutter跟随引导插件follow_the_leader的使用


Follow the Leader
Widgets following widgets.

这个项目是Flutter Bounty Hunters的一个概念验证。想要更多的跟随功能吗?今天就来资助一个里程碑吧!


开始使用

选择一个你想要跟随的部件,并用Leader部件包裹它。给Leader部件一个LeaderLink,以便与Follower共享。

Leader(
  link: _leaderLink,
  child: YourLeaderWidget(),
);

添加一个你想要跟随Leader的部件,并用Follower部件包裹它。

// Follower 出现在 Leader 的上方20像素。
Follower.withOffset(
  link: _leaderLink,
  offset: const Offset(0, -20),
  leaderAnchor: Alignment.topCenter,
  followerAnchor: Alignment.bottomCenter,
  child: YourFollowerWidget(),
);

Follower可以使用.withOffset()方法以固定距离跟随Leader,如上所示。或者,Follower可以通过使用.withAligner()方法来精确地定位在每一帧上。

// Follower 出现在对齐器指定的位置。
Follower.withAligner(
  link: _leaderLink,
  aligner: _aligner,
  child: YourFollowerWidget(),
);

为了限制你的Follower出现的位置,将一个boundary传递给你的Follower

// Follower 受到给定边界的约束。
Follower.withAligner(
  link: _leaderLink,
  aligner: _aligner,
  boundary: _boundary,
  child: YourFollowerWidget(),
);

构建多个不带布局的部件

在Flutter中构建跟随部件有点不同。通常,当我们构建多个部件时,我们会把它们放在一个布局容器中,比如ColumnRowStack。但是跟随部件不会遵循祖先布局规则。这就是重点。

follow_the_leader引入了一个新的容器部件,该部件构建子部件,但不尝试应用任何特定的布局规则。此部件的主要目的是让读者明白你并不是想布局这些子部件。

BuildInOrder(
  children: [
    MyContentWithALeader(),
    Follower.withOffset(),
    Follower.withDynamics(),
  ],
);

BuildInOrder部件按提供的顺序构建每个子部件。这一点很重要,因为Leader部件必须在其Follower之前构建。但是BuildInOrder不会对其子部件施加任何OffsetBuildInOrder将其父部件的约束向下传递给children


示例代码

以下是完整的示例代码:

import 'package:example/demo_hover.dart';
import 'package:example/demo_interactive_viewer.dart';
import 'package:example/demo_kitchen_sink.dart';
import 'package:example/demo_page_list_viewport.dart';
import 'package:example/demo_scaling.dart';
import 'package:example/demo_scrollables.dart';
import 'package:flutter/material.dart';
import 'package:follow_the_leader/follow_the_leader.dart';
import 'package:logging/logging.dart';

import 'demo_orbiting_circles.dart';

void main() {
  FtlLogs.initLoggers(Level.FINEST, {
    // FtlLogs.leader,
    // FtlLogs.follower,
    // FtlLogs.link,
    // FtlLogs.boundary,
    // appLog,
  });
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Follow the Leader Example',
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: const ExampleApp(),
    );
  }
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({Key? key}) : super(key: key);

  [@override](/user/override)
  State createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();

  _MenuItem _selectedMenu = _items.first;

  void _closeDrawer() {
    if (_scaffoldKey.currentState!.isDrawerOpen) {
      Navigator.of(context).pop();
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
      ),
      extendBodyBehindAppBar: true,
      body: Builder(builder: (bodyContext) {
        // 使用中间的Builder,以便给页面构建器提供有限的布局边界。
        return _selectedMenu.pageBuilder(bodyContext);
      }),
      drawer: _buildDrawer(),
    );
  }

  Widget _buildDrawer() {
    return Drawer(
      child: SingleChildScrollView(
        primary: false,
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 48),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              for (final item in _items) ...[
                _DrawerButton(
                  title: item.title,
                  onPressed: () => setState(() {
                    _selectedMenu = item;
                    _closeDrawer();
                  }),
                  isSelected: _selectedMenu == item,
                ),
                const SizedBox(height: 24),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

final _items = [
  _MenuItem(
    title: 'Follow the Leader',
    pageBuilder: (context) => const KitchenSinkDemo(),
  ),
  _MenuItem(
    title: 'Page List Viewport',
    pageBuilder: (context) => const PageListViewportDemo(),
  ),
  _MenuItem(
    title: 'Interactive Viewer',
    pageBuilder: (context) => const InteractiveViewerDemo(),
  ),
  _MenuItem(
    title: 'Hover',
    pageBuilder: (context) => const HoverDemo(),
  ),
  _MenuItem(
    title: 'Orbiting Circles',
    pageBuilder: (context) => const OrbitingCirclesDemo(),
  ),
  _MenuItem(
    title: 'Scaling',
    pageBuilder: (context) => const ScalingDemo(),
  ),
  _MenuItem(
    title: 'Scrollables',
    pageBuilder: (context) => const ScrollablesDemo(),
  ),
];

class _DrawerButton extends StatelessWidget {
  const _DrawerButton({
    Key? key,
    required this.title,
    this.isSelected = false,
    required this.onPressed,
  }) : super(key: key);

  final String title;
  final bool isSelected;
  final VoidCallback onPressed;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateColor.resolveWith((states) {
              if (isSelected) {
                return const Color(0xFFBBBBBB);
              }

              if (states.contains(MaterialState.hovered)) {
                return Colors.grey.withOpacity(0.1);
              }

              return Colors.transparent;
            }),
            foregroundColor: MaterialStateColor.resolveWith((states) => isSelected ? Colors.white : const Color(0xFFBBBBBB)),
            elevation: MaterialStateProperty.resolveWith((states) => 0),
            padding: MaterialStateProperty.resolveWith((states) => const EdgeInsets.all(16))),
        onPressed: isSelected ? null : onPressed,
        child: Center(child: Text(title)),
      ),
    );
  }
}

class _MenuItem {
  const _MenuItem({
    required this.title,
    required this.pageBuilder,
  });

  final String title;
  final WidgetBuilder pageBuilder;
}

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

1 回复

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


follow_the_leader 是一个用于在 Flutter 应用中实现引导效果的插件,通常用于创建带有箭头或提示框的引导流程,帮助用户了解应用的使用方法。以下是如何使用 follow_the_leader 插件的基本指南:

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 follow_the_leader 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  follow_the_leader: ^0.4.0  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 基本用法

follow_the_leader 的核心组件是 FollowTheLeader,它可以将一个 Leader(引导源)和一个 Follower(跟随者)关联起来,使得 Follower 能够跟随 Leader 的位置。

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

class MyGuidePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Follow The Leader Example'),
      ),
      body: Center(
        child: FollowTheLeader(
          leader: _buildLeader(),
          follower: _buildFollower(),
        ),
      ),
    );
  }

  Widget _buildLeader() {
    return Container(
      width: 100,
      height: 100,
      color: Colors.blue,
      child: Center(
        child: Text(
          'Leader',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }

  Widget _buildFollower() {
    return Container(
      width: 50,
      height: 50,
      decoration: BoxDecoration(
        color: Colors.red,
        shape: BoxShape.circle,
      ),
      child: Center(
        child: Text(
          'F',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }
}

3. 高级用法

FollowTheLeader 组件提供了多种配置选项,例如调整 Follower 的位置、动画效果等。

FollowTheLeader(
  leader: _buildLeader(),
  follower: _buildFollower(),
  offset: Offset(0, 50),  // Follower 相对于 Leader 的偏移量
  delay: Duration(milliseconds: 500),  // 延迟时间
  curve: Curves.easeInOut,  // 动画曲线
)

4. 在引导流程中使用

可以将 FollowTheLeader 用于引导流程中,例如在用户首次打开应用时显示引导提示。

class GuidedApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Guided App'),
        ),
        body: Column(
          children: [
            FollowTheLeader(
              leader: _buildLeader(),
              follower: _buildFollower(),
            ),
            ElevatedButton(
              onPressed: () {
                // 下一步引导
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

5. 自定义引导内容

Follower 可以是任何 Widget,因此你可以自定义引导提示的内容和样式。

Widget _buildFollower() {
  return Container(
    padding: EdgeInsets.all(8),
    decoration: BoxDecoration(
      color: Colors.green,
      borderRadius: BorderRadius.circular(8),
    ),
    child: Text(
      'This is a helpful tip!',
      style: TextStyle(color: Colors.white),
    ),
  );
}

6. 处理引导结束

当用户完成引导时,可以通过回调函数来处理引导结束的逻辑。

FollowTheLeader(
  leader: _buildLeader(),
  follower: _buildFollower(),
  onCompleted: () {
    // 引导完成后的操作
    print('Guide completed!');
  },
)
回到顶部