0%

Flutter-Dart全局可拖动悬浮球

当我们全局都需要用到某个设定且随时需要根据需求改变时,那么全局悬浮球是一个最好的选择(可拖动),参考其他大佬的文章,优化封装了一个简易的悬浮球,记录一下0.0。

Dart全局悬浮球

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import 'dart:math';

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

class PubScaffold extends StatefulWidget {
final Widget child;
PubScaffold({this.child});

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

class _PubScaffoldState extends State<PubScaffold> {
List _bottomSheetList = ['x','y','z'];
bool dragAble = false;
// bottomSheet是否已经显示
bool isShow = false;

// 静止状态下的offset
Offset idleOffset = Offset(0, 0);
// 本次移动的offset
Offset moveOffset = Offset(0, 0);
// 最后一次down事件的offset
Offset lastStartOffset = Offset(0, 0);

int count = 0;
static OverlayEntry entry;

/// 列表点击事件
selectItemCallBack(e) {
print('选中${e}');
if (isShow) {
Navigator.pop(context);
}
}

/// 显示一个底部弹窗,这里是一个测试列表。
showSelectList() async {
KeyboardBack.keyboardBack();
if (isShow) {
Navigator.pop(context);
return;
}
var flag = await showModalBottomSheet(
isScrollControlled: true,
context: context,
enableDrag: false,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0),
),
),
builder: (BuildContext context) {
isShow = true;
return SingleChildScrollView(
child: Container(
// 返回一个有高度的组件,对话框高度就是此高度。
padding: MediaQuery.of(context).viewInsets,
height: 285,
child: ListView(
children: _bottomSheetList.map((e) =>
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFe3e3e3)),
),
),
child: ListTile(
onTap: () => selectItemCallBack(e),
title: Text(e),
),
)).toList()
),
);
),
},
);
if (flag == null) {
isShow = false;
}
}

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 显示悬浮按钮
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return widget.child;
},
);
}

// 悬浮按钮,可以拖拽(可自定义样式)
void _insertOverlay(BuildContext context) {
entry = OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
double maxWidth = size.width - 50;
double maxHeight = size.height - 50;
double defaultX = size.width - 70;
double defaultY = size.height - 310;
return Positioned(
top: draggable
? (moveOffset.dy < 0 // 沉浸式如果超出屏幕,默认为 0。
? 0
: moveOffset.dy > maxHeight // 其他情况超出屏幕默认为 maxHeight。
? maxHeight
: moveOffset.dy)
: defaultY,
left: draggable ? (moveOffset.dx > maxWidth ? maxWidth : moveOffset.dx) : defaultX,
child: GestureDetector(
// 移动开始
onPanStart: (DragStartDetails details) {
setState(() {
lastStartOffset = details.globalPosition;
dragAble = true;
});
if (count <= 1) {
count++;
}
},
// 移动中
onPanUpdate: (DragUpdateDetails details) {
setState(() {
moveOffset = details.globalPosition - lastStartOffset + idleOffset;
if (count > 1) {
moveOffset = Offset(max(0, moveOffset.dx), moveOffset.dy);
} else {
moveOffset = Offset(max(0, moveOffset.dx + (size.width - 70)), moveOffset.dy + (size.height - 310));
}
});
},
// 移动结束
onPanEnd: (DragEndDetails detail) {
setState(() {
idleOffset = moveOffset * 1;
});
},
child: BallContainer(
onPressed: () => showSelectList(),
),
),
);
});
}
}

/// 悬浮按钮的样式
class BallContainer extends StatelessWidget {
final Function onPressed;
BallContainer({this.onPressed});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: GestureDetector(
onTap: onPressed,
child: Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0x666889E6),
),
child: Text(
'球体内容',
style: TextStyle(color: Colors.white),
),
),
),
);
}
}

/// 关闭键盘,避免键盘与弹出列表冲突。
class KeyboardBack {
static BuildContext context = navigatorKey.currentState.overlay.context;
static FocusScopeNode currentFocus = FocusScope.of(context);
static void keyboardBack() {
if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus.unfocus();
}
}
}

使用

在主程序 main.dart 套上我们的 PubScaffold 即可。

1
2
3
4
5
6
7
8
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
builder: EasyLoading.init(),
home: PubScaffold(child: ...略)
);
}
bulb