Flutter快速操作菜单插件freestyle_speed_dial的使用

Flutter快速操作菜单插件freestyle_speed_dial的使用

这个包提供了一个名为SpeedDialBuilder的轻量级小部件,允许你构建各种风格的快速操作按钮。

它提供了对子按钮及其二级小部件(如按钮标签)外观、布局和动画的完全控制。

示例

你可以尝试在线演示或运行示例项目来查看一些示例布局和动画。

使用方法

构建主按钮

每个快速操作按钮的起点是一个主切换按钮。使用buttonBuilder创建自定义的切换按钮。你可以在其中使用任何组合的部件。重要的是,在用户交互时,其中一个部件必须调用由生成器提供的toggle函数。 为了反映快速操作按钮的状态,可以根据isActive参数改变你的部件。在两种状态之间过渡时,可以考虑使用AnimatedSwitcher或其他Flutter隐式动画小部件

基本示例代码:

SpeedDialBuilder(
  ...
  buttonBuilder: (context, isActive, toggle) => FloatingActionButton(
    onPressed: toggle,
    child: Icon( isActive ? Icons.close : Icons.add )
  )
)

添加项并创建子按钮

速度拨盘的项必须传递给items属性。在那里,你可以直接传递你的部件,或者其他数据类型,比如自定义容器类。传递的项将在itemBuilder中可用,你可以在其中进一步指定它们的外观、布局和动画。

现在让我们关注一个简单的例子,我们直接传递两个项作为小的FloatingActionButton部件。

SpeedDialBuilder(
  ...
  items: [
    FloatingActionButton.small(
      onPressed: () {},
      child: const Icon(Icons.hub),
    ),
    FloatingActionButton.small(
      onPressed: () {},
      child: const Icon(Icons.file_download),
    )
  ]
)

itemBuilder中,我们使用提供的项索引和FractionalTranslation部件垂直定位我们的项。

此外,我们为每个项包装一个ScaleTransition,并将提供的animation对象传递给它,以便为每个项获得漂亮的进出过渡。你可以通过SpeedDialBuilderanimationOverlapreversedurationreverseDurationcurvereverseCurve属性进一步控制动画。

SpeedDialBuilder(
  ...
  buttonAnchor: Alignment.topCenter,
  itemAnchor: Alignment.bottomCenter,
  itemBuilder: (context, Widget item, i, animation) => FractionalTranslation(
    translation: Offset(0, -i.toDouble()),
    child: ScaleTransition(
      scale: animation,
      child: item
    )
  )
)

你可能已经注意到,两个额外的属性buttonAnchoritemAnchor溜了进来。这些只控制初始项位置,然后在itemBuilder中进行转换。在这种情况下,它们位于主按钮正上方。 可以把这想象成定义两个点,一个是固定的主按钮上的点,另一个是每个项上的点。这两个点通过重新定位项被带到一起。

完整示例代码

SpeedDialBuilder(
  buttonAnchor: Alignment.topCenter,
  itemAnchor: Alignment.bottomCenter,
  buttonBuilder: (context, isActive, toggle) => FloatingActionButton(
    onPressed: toggle,
    child: Icon( isActive ? Icons.close : Icons.add )
  ),
  itemBuilder: (context, Widget item, i, animation) => FractionalTranslation(
    translation: Offset(0, -i.toDouble()),
    child: ScaleTransition(
      scale: animation,
      child: item
    )
  ),
  items: [
    FloatingActionButton.small(
      onPressed: () {},
      child: const Icon(Icons.hub),
    ),
    FloatingActionButton.small(
      onPressed: () {},
      child: const Icon(Icons.file_download),
    )
  ]
)

显示项下的标签

要自由地显示项下方的标签,你可以使用secondaryItemBuilder。在这种情况下,你需要将描述按钮和标签的数据对象而不是单个部件传递给items属性。你应该在生成器方法中构建你的部件(按钮和标签)。 或者,你也可以直接在itemBuilder中包含标签,例如使用一行。然而,这种方法可能会使项的对齐变得更难。

下面的示例使用secondaryItemBuilder与Flutters CompositedTransformTargetCompositedTransformFollower部件结合,显示每个项下方的标签。

SpeedDialBuilder(
  buttonAnchor: Alignment.topCenter,
  itemAnchor: Alignment.bottomCenter,
  buttonBuilder: (context, isActive, toggle) => FloatingActionButton(
    onPressed: toggle,
    child: Icon( isActive ? Icons.close : Icons.add )
  ),
  itemBuilder: (context, Tuple3<IconData, String, LayerLink> item, i, animation) {
    return FractionalTranslation(
      translation: Offset(0, -i.toDouble()),
      child: CompositedTransformTarget(
        link: item.item3,
        child: ScaleTransition(
          scale: animation,
          child: FloatingActionButton.small(
            onPressed: () {},
            child: Icon(item.item1),
          ),
        )
      )
    );
  },
  secondaryItemBuilder: (context, Tuple3<IconData, String, LayerLink> item, i, animation) {
    return CompositedTransformFollower(
      link: item.item3,
      targetAnchor: Alignment.centerRight,
      followerAnchor: Alignment.centerLeft,
      child: FadeTransition(
        opacity: animation,
        child: Card(
          margin: const EdgeInsets.only( left: 10 ),
          child: Padding(
            padding: const EdgeInsets.all(5),
            child: Text(item.item2),
          )
        )
      )
    );
  },
  items: [
    Tuple3<IconData, String, LayerLink>(
      Icons.hub, 'Hub', LayerLink()
    ),
    Tuple3<IconData, String, LayerLink>(
      Icons.track_changes, 'Track', LayerLink()
    )
  ]
)

完整示例Demo

以下是一个完整的示例代码,展示了如何使用freestyle_speed_dial插件创建不同类型的快速操作按钮。

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:freestyle_speed_dial/freestyle_speed_dial.dart';
import 'package:tuple/tuple.dart';

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

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData.dark().copyWith(
            scaffoldBackgroundColor: const Color.fromARGB(255, 18, 32, 47),
            materialTapTargetSize: MaterialTapTargetSize.padded),
        home: const ExamplePage());
  }
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: EdgeInsets.symmetric(
              horizontal: min(200, MediaQuery.of(context).size.width / 3),
              vertical: 200),
          child: Wrap(
            spacing: 200,
            runSpacing: 200,
            children: [
              // 示例1(垂直弹出)
              // 简单的速度拨盘,其中每个子按钮/项从其自身的最终位置开始动画。
              SpeedDialBuilder(
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOutCubicEmphasized,
                    turns: isActive ? 0.125 : 0,
                    child: const Icon(Icons.add),
                  ),
                ),
                buttonAnchor: Alignment.topCenter,
                itemAnchor: Alignment.bottomCenter,
                itemBuilder: (context, Widget item, i, animation) =>
                    FractionalTranslation(
                  translation: Offset(0, -i.toDouble()),
                  child: ScaleTransition(scale: animation, child: item),
                ),
                items: [
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.hub),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.file_download),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.wallet),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.sd_card),
                  )
                ],
              ),

              // 示例2(垂直原地滑入)
              // 简单的速度拨盘,其中每个子按钮/项从FAB的位置开始动画到其最终位置。
              SpeedDialBuilder(
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                      duration: const Duration(milliseconds: 300),
                      curve: Curves.easeInOutCubicEmphasized,
                      turns: isActive ? 0.125 : 0,
                      child: const Icon(Icons.add)),
                ),
                curve: Curves.easeInOutCubicEmphasized,
                reverse: true,
                itemBuilder: (context, Widget item, i, animation) {
                  final offsetAnimation = Tween<Offset>(
                    begin: Offset.zero,
                    end: Offset(0, -i - 1),
                  ).animate(animation);
                  return SlideTransition(
                    position: offsetAnimation,
                    child: FadeTransition(
                      opacity: animation,
                      child: item,
                    ),
                  );
                },
                items: [
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.hub),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.file_download),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.wallet),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.sd_card),
                  ),
                ],
              ),

              // 示例3(对角线原地滑入)
              // 简单的速度拨盘,其中每个子按钮/项从FAB的位置开始动画到其最终位置。
              SpeedDialBuilder(
                buttonAnchor: Alignment.center,
                itemAnchor: Alignment.center,
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOutCubicEmphasized,
                    turns: isActive ? 0.125 : 0,
                    child: const Icon(Icons.add),
                  ),
                ),
                curve: Curves.easeInOutCubicEmphasized,
                reverse: true,
                itemBuilder: (context, Widget item, i, animation) {
                  // 每个项的相对半径单位
                  const radius = 1.3;
                  // 弧度角度
                  const angle = -3 / 4 * pi;

                  final targetOffset = Offset(
                      (i + radius) * cos(angle), (i + radius) * sin(angle));

                  final offsetAnimation =
                      Tween<Offset>(begin: Offset.zero, end: targetOffset)
                          .animate(animation);
                  return SlideTransition(
                    position: offsetAnimation,
                    child: FadeTransition(
                      opacity: animation,
                      child: item,
                    ),
                  );
                },
                items: [
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.hub),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.file_download),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.wallet),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.sd_card),
                  )
                ],
              ),

              // 示例4(径向原地滑入)
              // 简单的速度拨盘,其中每个子按钮/项从FAB的位置开始动画到其最终位置。
              SpeedDialBuilder(
                buttonAnchor: Alignment.center,
                itemAnchor: Alignment.center,
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOutCubicEmphasized,
                    turns: isActive ? 0.125 : 0,
                    child: const Icon(Icons.add),
                  ),
                ),
                itemBuilder: (context, Widget item, i, animation) {
                  // 每个项的相对半径单位
                  const radius = 1.3;
                  // 弧度角度
                  final angle = i * (pi / 4) + pi;

                  final targetOffset = Offset(
                    radius * cos(angle),
                    radius * sin(angle),
                  );

                  final offsetAnimation = Tween<Offset>(
                    begin: Offset.zero,
                    end: targetOffset,
                  ).animate(animation);

                  return SlideTransition(
                    position: offsetAnimation,
                    child: FadeTransition(
                      opacity: animation,
                      child: item,
                    ),
                  );
                },
                items: [
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.hub),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.file_download),
                  ),
                  FloatingActionButton.small(
                    onPressed: () {},
                    child: const Icon(Icons.wallet),
                  ),
                ],
              ),

              // 示例5(垂直弹出带标签)
              // 高级速度拨盘,其中每个子按钮/项都有一个额外的标签。
              // 项和标签都从它们的目标位置开始动画。
              SpeedDialBuilder(
                buttonAnchor: Alignment.topCenter,
                itemAnchor: Alignment.bottomCenter,
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOutCubicEmphasized,
                    turns: isActive ? 0.125 : 0,
                    child: const Icon(Icons.add),
                  ),
                ),
                itemBuilder: (context, Tuple3<IconData, String, LayerLink> item,
                    i, animation) {
                  return FractionalTranslation(
                    translation: Offset(0, -i.toDouble()),
                    child: CompositedTransformTarget(
                      link: item.item3,
                      child: ScaleTransition(
                        scale: animation,
                        child: FloatingActionButton.small(
                          onPressed: () {},
                          child: Icon(item.item1),
                        ),
                      ),
                    ),
                  );
                },
                secondaryItemBuilder: (context,
                    Tuple3<IconData, String, LayerLink> item, i, animation) {
                  return CompositedTransformFollower(
                    link: item.item3,
                    targetAnchor: Alignment.centerRight,
                    followerAnchor: Alignment.centerLeft,
                    child: FadeTransition(
                      opacity: animation,
                      child: Card(
                        margin: const EdgeInsets.only(left: 10),
                        child: Padding(
                          padding: const EdgeInsets.all(5),
                          child: Text(item.item2),
                        ),
                      ),
                    ),
                  );
                },
                items: [
                  Tuple3<IconData, String, LayerLink>(
                      Icons.hub, 'Hub', LayerLink()),
                  Tuple3<IconData, String, LayerLink>(
                      Icons.track_changes, 'Track', LayerLink()),
                  Tuple3<IconData, String, LayerLink>(
                      Icons.ice_skating_outlined, 'Ice', LayerLink())
                ],
              ),

              // 示例6(垂直原地滑入带标签)
              // 高级速度拨盘,其中每个子按钮/项都有一个额外的标签。
              // 项和标签都从它们的目标位置开始动画。
              SpeedDialBuilder(
                buttonBuilder: (context, isActive, toggle) =>
                    FloatingActionButton(
                  onPressed: toggle,
                  child: AnimatedRotation(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOutCubicEmphasized,
                    turns: isActive ? 0.125 : 0,
                    child: const Icon(Icons.add),
                  ),
                ),
                reverse: true,
                itemBuilder: (context, Tuple3<IconData, String, LayerLink> item,
                    i, animation) {
                  final offsetAnimation = Tween<Offset>(
                    begin: Offset.zero,
                    end: Offset(0, -i - 1),
                  ).animate(animation);
                  return SlideTransition(
                    position: offsetAnimation,
                    child: FadeTransition(
                      opacity: animation,
                      child: CompositedTransformTarget(
                        link: item.item3,
                        child: FloatingActionButton.small(
                          onPressed: () {},
                          child: Icon(item.item1),
                        ),
                      ),
                    ),
                  );
                },
                secondaryItemBuilder: (context,
                    Tuple3<IconData, String, LayerLink> item, i, animation) {
                  return CompositedTransformFollower(
                    link: item.item3,
                    targetAnchor: Alignment.centerRight,
                    followerAnchor: Alignment.centerLeft,
                    child: FadeTransition(
                      opacity: animation,
                      child: Card(
                        margin: const EdgeInsets.only(left: 10),
                        child: Padding(
                          padding: const EdgeInsets.all(5),
                          child: Text(item.item2),
                        ),
                      ),
                    ),
                  );
                },
                items: [
                  Tuple3<IconData, String, LayerLink>(
                    Icons.hub,
                    'Hub',
                    LayerLink(),
                  ),
                  Tuple3<IconData, String, LayerLink>(
                    Icons.track_changes,
                    'Track',
                    LayerLink(),
                  ),
                  Tuple3<IconData, String, LayerLink>(
                    Icons.ice_skating_outlined,
                    'Ice',
                    LayerLink(),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter快速操作菜单插件freestyle_speed_dial的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter快速操作菜单插件freestyle_speed_dial的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


freestyle_speed_dial 是一个用于 Flutter 的快速操作菜单插件,类似于浮动动作按钮(FAB)的扩展,允许用户快速访问多个操作项。它提供了一种直观的方式在应用中展示额外的操作选项,通常用于需要频繁操作或快速切换功能的场景。

安装

首先,你需要在 pubspec.yaml 文件中添加 freestyle_speed_dial 依赖:

dependencies:
  flutter:
    sdk: flutter
  freestyle_speed_dial: ^1.0.0  # 请检查最新版本

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

基本使用

以下是一个简单的示例,展示如何在 Flutter 应用中使用 freestyle_speed_dial

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Freestyle Speed Dial Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SpeedDialExample(),
    );
  }
}

class SpeedDialExample extends StatefulWidget {
  [@override](/user/override)
  _SpeedDialExampleState createState() => _SpeedDialExampleState();
}

class _SpeedDialExampleState extends State<SpeedDialExample> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Freestyle Speed Dial Example'),
      ),
      body: Center(
        child: Text('Press the floating button to see the speed dial!'),
      ),
      floatingActionButton: FreestyleSpeedDial(
        child: Icon(Icons.add),
        options: [
          SpeedDialOption(
            icon: Icons.message,
            label: "Message",
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text("Message action")),
              );
            },
          ),
          SpeedDialOption(
            icon: Icons.call,
            label: "Call",
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text("Call action")),
              );
            },
          ),
          SpeedDialOption(
            icon: Icons.email,
            label: "Email",
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text("Email action")),
              );
            },
          ),
        ],
      ),
    );
  }
}
回到顶部