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
对象传递给它,以便为每个项获得漂亮的进出过渡。你可以通过SpeedDialBuilder
的animationOverlap
、reverse
、duration
、reverseDuration
、curve
和reverseCurve
属性进一步控制动画。
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
)
)
)
你可能已经注意到,两个额外的属性buttonAnchor
和itemAnchor
溜了进来。这些只控制初始项位置,然后在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 CompositedTransformTarget
和CompositedTransformFollower
部件结合,显示每个项下方的标签。
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
更多关于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")),
);
},
),
],
),
);
}
}