原文

https://medium.com/flutterdevs/draggable-floating-action-button-in-flutter-2149a7e47f06

参考

正文

Flutter 允许您使用 FloatingActionButton 小部件添加一个浮动操作按钮。尽管如此,它不允许你拖动按钮。考虑一下你需要让它可拖动的可能性。本文有一个模型,它公开了制作一个浮动操作按钮需要做的事情,这个按钮可以在屏幕的任何地方拖动,只要它在父窗口小部件中。

在这个博客中,我们将探索 Flutter 的拖动浮动按钮。我们将看到如何实现一个演示程序的拖曳浮动行动按钮,并显示如何创建您的 Flutter 应用程序。

简介

下面的演示视频显示了如何创建一个可拖动的漂浮动作按钮在 Flutter。它显示了如何拖动浮动行动按钮将在您的 Flutter 应用程序的工作。它显示当代码成功运行时,用户将一个浮动操作按钮拖动到屏幕周围的任何地方,只要它在父窗口小部件中。它会显示在你的设备上。

如何实现 dart 文件中的代码

你需要分别在你的代码中实现它:

在 lib 文件夹中创建一个名为 main.dart 的新 dart 文件。

首先,我们将创建一个 Globalkey,并将其命名为 _parentKey

1
final GlobalKey _parentKey = GlobalKey();

在正文中,我们将添加一个具有高度和宽度的 Container 小部件。它是子属性,我们将添加 Stack 小部件。在这个小部件中,我们将添加一个键、文本和一个 DraggableFloatingActionButton ()。在按钮内部,我们将添加一个具有高度和宽度的容器。在其子属性中添加图像。此外,我们还将添加 initialOffset、父键和 onPressed。我们将深入定义下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Container(
width: 300,
height: 300,
child: Stack(
key: _parentKey,
children: [
Container(color: Colors.cyan),
Center(
child: const Text(
"FlutterDev's.com",
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
DraggableFloatingActionButton(
child: Container(
width: 60,
height: 60,
decoration: ShapeDecoration(
shape: CircleBorder(),
color: Colors.white,
),
child: Image.asset("assets/logo.png"),
),
initialOffset: const Offset(120, 70),
parentKey: _parentKey,
onPressed: () {},
),
],
),
)

创建一个名为 draggable_floating_action_button.dart 的代码文件。

我们将为这样的小部件创建一个类。我们需要处理的主要问题是使按钮在指针之后可拖动的能力。可以使用的小部件之一是监听器,它可以识别指针移动事件并给出移动细节。基本上,按钮应该作为侦听器的子级进行包装。

Listener 小部件具有 onPointerMove 争用,可以利用该争用来传递一个回调,该回调在指针移动时将被考虑。回调函数应该有一个边界 PointerMoveEvent,其中包含 x 和 y 标题中的开发增量(增量)。Dx 和 delta。).捕获物的偏移量应该通过移动三角洲来刷新。

下面是制作可拖动浮动操作按钮的类。它有几个争论,包括子,initialOffset,和 onPressed。子小部件是利用依赖于当前偏移量的定位小部件交付的。此外,它还被包装为监听器小部件的子部件。此外,还有一个策略 _updatePosition ,根据移动的增量刷新当前的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class DraggableFloatingActionButton extends StatefulWidget {

final Widget child;
final Offset initialOffset;
final VoidCallback onPressed;

DraggableFloatingActionButton({
required this.child,
required this.initialOffset,
required this.onPressed,
});

@override
State<StatefulWidget> createState() => _DraggableFloatingActionButtonState();
}

class _DraggableFloatingActionButtonState extends State<DraggableFloatingActionButton> {

bool _isDragging = false;
late Offset _offset;

@override
void initState() {
super.initState();
_offset = widget.initialOffset;
}

void _updatePosition(PointerMoveEvent pointerMoveEvent) {
double newOffsetX = _offset.dx + pointerMoveEvent.delta.dx;
double newOffsetY = _offset.dy + pointerMoveEvent.delta.dy;

setState(() {
_offset = Offset(newOffsetX, newOffsetY);
});
}

@override
Widget build(BuildContext context) {
return Positioned(
left: _offset.dx,
top: _offset.dy,
child: Listener(
onPointerMove: (PointerMoveEvent pointerMoveEvent) {
_updatePosition(pointerMoveEvent);

setState(() {
_isDragging = true;
});
},
onPointerUp: (PointerUpEvent pointerUpEvent) {
print('onPointerUp');

if (_isDragging) {
setState(() {
_isDragging = false;
});
} else {
widget.onPressed();
}
},
child: widget.child,
),
);
}
}

您需要向父部件添加一个键,并将其传递给 DraggableFloatingActionButton 部件。您可以从 currentContext 属性中获得 RenderBox,该属性具有 findRenderObject 策略。然后,在这一点上,您可以从 RenderBox 的 size 属性获得父级的大小。您应该谨慎,因为应该在构建树之后调用 findRenderObject 技术。随后,您需要利用 WidgetsBinding 的 addPostFrameCallback 来调用它。

技术的 _updatePosition 也应该改变。如果新的偏移量小于最小偏移量,则该值必须设置为最小偏移量。如果新的偏移量比最大偏移量更值得注意,则该值必须设置为最大偏移量。对于 x 轴和 y 轴都需要这样做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class DraggableFloatingActionButton extends StatefulWidget {

final Widget child;
final Offset initialOffset;
final VoidCallback onPressed;

DraggableFloatingActionButton({
required this.child,
required this.initialOffset,
required this.onPressed,
});

@override
State<StatefulWidget> createState() => _DraggableFloatingActionButtonState();
}

class _DraggableFloatingActionButtonState extends State<DraggableFloatingActionButton> {

final GlobalKey _key = GlobalKey();

bool _isDragging = false;
late Offset _offset;
late Offset _minOffset;
late Offset _maxOffset;

@override
void initState() {
super.initState();
_offset = widget.initialOffset;

WidgetsBinding.instance?.addPostFrameCallback(_setBoundary);
}

void _setBoundary(_) {
final RenderBox parentRenderBox = widget.parentKey.currentContext?.findRenderObject() as RenderBox;
final RenderBox renderBox = _key.currentContext?.findRenderObject() as RenderBox;

try {
final Size parentSize = parentRenderBox.size;
final Size size = renderBox.size;

setState(() {
_minOffset = const Offset(0, 0);
_maxOffset = Offset(
parentSize.width - size.width,
parentSize.height - size.height
);
});
} catch (e) {
print('catch: $e');
}
}

void _updatePosition(PointerMoveEvent pointerMoveEvent) {
double newOffsetX = _offset.dx + pointerMoveEvent.delta.dx;
double newOffsetY = _offset.dy + pointerMoveEvent.delta.dy;

if (newOffsetX < _minOffset.dx) {
newOffsetX = _minOffset.dx;
} else if (newOffsetX > _maxOffset.dx) {
newOffsetX = _maxOffset.dx;
}

if (newOffsetY < _minOffset.dy) {
newOffsetY = _minOffset.dy;
} else if (newOffsetY > _maxOffset.dy) {
newOffsetY = _maxOffset.dy;
}

setState(() {
_offset = Offset(newOffsetX, newOffsetY);
});
}

@override
Widget build(BuildContext context) {
return Positioned(
left: _offset.dx,
top: _offset.dy,
child: Listener(
onPointerMove: (PointerMoveEvent pointerMoveEvent) {
_updatePosition(pointerMoveEvent);

setState(() {
_isDragging = true;
});
},
onPointerUp: (PointerUpEvent pointerUpEvent) {
print('onPointerUp');

if (_isDragging) {
setState(() {
_isDragging = false;
});
} else {
widget.onPressed();
}
},
child: Container(
key: _key,
child: widget.child,
),
),
);
}
}

当我们运行应用程序时,我们应该得到屏幕的输出,就像下面的屏幕截图一样。

全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_draggable_floating/draggable_floating_action_button.dart';
import 'package:flutter_draggable_floating/splash_screen.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Splash(),
);
}
}

class DraggableFloatingActionButtonDemo extends StatelessWidget {

final GlobalKey _parentKey = GlobalKey();

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text('Flutter Draggable Floating Action Button'),
backgroundColor: Colors.cyan,
),
body: Column(
children: [
Container(
height: 150,
),
Container(
width: 300,
height: 300,
child: Stack(
key: _parentKey,
children: [
Container(color: Colors.cyan),
Center(
child: const Text(
"FlutterDev's.com",
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
DraggableFloatingActionButton(
child: Container(
width: 60,
height: 60,
decoration: ShapeDecoration(
shape: CircleBorder(),
color: Colors.white,
),
child: Image.asset("assets/logo.png"),
),
initialOffset: const Offset(120, 70),
parentKey: _parentKey,
onPressed: () {},
),
],
),
)
],
),
);
}
}

结语

在这篇文章中,我已经解释了可拖动浮动按钮的基本结构,您可以根据自己的选择修改这个代码。这是一个小的介绍可拖动浮动操作按钮对用户交互从我这边,它的工作使用 Flutter。

我希望这个博客将提供给您充分的信息,尝试在您的 Flutter 项目的拖曳浮动行动按钮。我们将向您展示介绍是什么?.这就是在 Flutter 制作一个可拖动的浮动按钮的方法。从根本上讲,您可以利用 Listener 小部件来区分指针移动事件,并根据开发增量更新按钮偏移量。监听器小部件同样支持区分指针事件,除非按钮最近被拖动,否则应该在这些事件上执行按钮的活动。同样,您需要获得父按钮和按钮的大小,以防止按钮超出父按钮的范围。所以请尝试一下。


© 猫哥

往期

开源

GetX Quick Start

https://github.com/ducafecat/getx_quick_start

新闻客户端

https://github.com/ducafecat/flutter_learn_news

strapi 手册译文

https://getstrapi.cn

微信讨论群 ducafecat

系列集合

译文

https://ducafecat.tech/categories/%E8%AF%91%E6%96%87/

开源项目

https://ducafecat.tech/categories/%E5%BC%80%E6%BA%90/

Dart 编程语言基础

https://space.bilibili.com/404904528/channel/detail?cid=111585

Flutter 零基础入门

https://space.bilibili.com/404904528/channel/detail?cid=123470

Flutter 实战从零开始 新闻客户端

https://space.bilibili.com/404904528/channel/detail?cid=106755

Flutter 组件开发

https://space.bilibili.com/404904528/channel/detail?cid=144262

Flutter Bloc

https://space.bilibili.com/404904528/channel/detail?cid=177519

Flutter Getx4

https://space.bilibili.com/404904528/channel/detail?cid=177514

Docker Yapi

https://space.bilibili.com/404904528/channel/detail?cid=130578