使用Overlay + GestureDetector 制作一个全局可拖拽悬浮按钮
1.场景
现在需要做一个Test按钮,悬浮在所有页面之上,并且可以拖拽。
2.思路
1)悬浮按钮可以使用flutter提供的Overlay + OverlayEntry 组合实现
2)拖拽功能可以使用GestureDetector手势按钮或者Draggable实现(PS:我做了一版Draggable实现的,但是发现它会有原本的widget浮在原地,显然不是我要的效果)
3)点击的时候我是让它弹出一个底部弹框,这里你们可以*发挥,本篇文章不做多余赘述
3.具体实现
app.dart/main.dart(应用程序入口)
@override
Widget build(BuildContext context) {
return MaterialApp(
home: PubScaffold(
child: OKToast(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
...
),
),
),
);
}
这里的PubScaffold
就是我封装的一个悬浮按钮组件,把它包裹在MaterialApp外面,就可以实现悬浮在所有的组件之上的一个按钮啦(当然也可以不是按钮,具体样式可以自己定义)。下面我们来看一下PubScaffold
中的代码吧~
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:printer/foundation/method_channel/poooli_navigator.dart';
class PubScaffold extends StatefulWidget {
final Widget child;
PubScaffold({this.child});
@override
_PubScaffoldState createState() => _PubScaffoldState();
}
class _PubScaffoldState extends State<PubScaffold> {
bool draggable = false;
//静止状态下的offset
Offset idleOffset = Offset(0, 0);
//本次移动的offset
Offset moveOffset = Offset(0, 0);
//最后一次down事件的offset
Offset lastStartOffset = Offset(0, 0);
int count = 0;
final List<String> testWidgetList = [
'测试1',
'测试2',
];
testAppFun(e) {
// TODO: 你的代码逻辑
}
// 显示一个底部弹窗,这里是一个测试列表
showTestList() {
showModalBottomSheet(
context: context,
enableDrag: false,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
builder: (BuildContext context) {
return ListView(
children: testWidgetList
.map(
(e) => Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFe3e3e3)),
),
),
child: ListTile(
onTap: () => testAppFun(e),
title: Text(e),
),
),
)
.toList(),
);
},
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 显示悬浮按钮
WidgetsBinding.instance
.addPostFrameCallback((_) => _insertOverlay(context));
return widget.child;
},
);
}
// 悬浮按钮,可以拖拽(可自定义样式)
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
top: draggable ? moveOffset.dy : size.height - 102,
left: draggable ? moveOffset.dx : size.width - 72,
child: GestureDetector(
// 移动开始
onPanStart: (DragStartDetails details) {
setState(() {
lastStartOffset = details.globalPosition;
draggable = 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 - 72)),
moveOffset.dy + (size.height - 102));
}
});
},
// 移动结束
onPanEnd: (DragEndDetails detail) {
setState(() {
idleOffset = moveOffset * 1;
});
},
child: TestContainer(
onPress: () => showTestList(),
),
),
);
}),
);
}
}
// 悬浮按钮的样式
class TestContainer extends StatelessWidget {
final Function onPress;
TestContainer({this.onPress});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: GestureDetector(
onTap: onPress,
child: Container(
width: 56,
height: 56,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.green[600],
),
child: Text(
"Test",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
1.全局悬浮按钮
这里我们用的是flutter自带的material库中的Overlay组件,具体使用方法如下:
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
使用Overlay.of(context).insert
和OverlayEntry
自定义构建一个悬浮组件,当你想要显示它的时候只需在builder中插入以下代码
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
参考:https://*.com/questions/57069641/how-to-overlay-a-widget-on-top-of-a-flutter-app
2.拖拽
使用GestureDetector手势组件实现。具体实现参考代码