方法一
image-20210822124957439
Offset _offset = Offset.zero;
Scaffold(
body: Stack(
children: [
_pageList[_currentIndex],
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) =>
setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.orange,
child: Icon(Icons.add),
),
),
),
],
),
方法二
WidgetsBinding.instance
.addPostFrameCallback((_) => _insertOverlay(context));
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),
),
),
),
);
}),
);
}
方法三
1.場(chǎng)景
現(xiàn)在需要做一個(gè)Test按鈕宿接,懸浮在所有頁(yè)面之上,并且可以拖拽厨埋。
2.思路
1)懸浮按鈕可以使用flutter提供的Overlay + OverlayEntry 組合實(shí)現(xiàn)
2)拖拽功能可以使用GestureDetector手勢(shì)按鈕或者Draggable實(shí)現(xiàn)(PS:我做了一版Draggable實(shí)現(xiàn)的饿悬,但是發(fā)現(xiàn)它會(huì)有原本的widget浮在原地,顯然不是我要的效果)
3)點(diǎn)擊的時(shí)候我是讓它彈出一個(gè)底部彈框叽唱,這里你們可以自由發(fā)揮屈呕,本篇文章不做多余贅述
PubScaffold(
child: MaterialApp(
theme: CustomTheme.lightTheme,
darkTheme: CustomTheme.darkTheme,
themeMode: currentTheme.currentTheme,
home: Scaffold(
body: Stack(
children: [
_pageList[_currentIndex],
// Positioned(
// left: _offset.dx,
// top: _offset.dy,
// child: GestureDetector(
// onPanUpdate: (d) =>
// setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
// child: FloatingActionButton(
// onPressed: () {},
// backgroundColor: Colors.orange,
// child: Icon(Icons.add),
// ),
// ),
// ),
],
),
bottomNavigationBar: CurvedNavigationBar(
// key: _bottomNavigationKey,
index: 0,
height: 60.0,
items: <Widget>[
Icon(Icons.home, size: 30),
Icon(Icons.list, size: 30),
Icon(Icons.compare_arrows, size: 30),
// Icon(Icons.call_split, size: 30),
],
color: Colors.white,
buttonBackgroundColor: Colors.white,
backgroundColor: Colors.blueAccent,
animationCurve: Curves.easeInOut,
animationDuration: Duration(milliseconds: 600),
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
// letIndexChange: (index) => true,
),
),
),
);
這里的PubScaffold
就是我封裝的一個(gè)懸浮按鈕組件,把它包裹在MaterialApp外面棺亭,就可以實(shí)現(xiàn)懸浮在所有的組件之上的一個(gè)按鈕啦(當(dāng)然也可以不是按鈕虎眨,具體樣式可以自己定義)。下面我們來看一下PubScaffold
中的代碼吧~
import 'dart:math';
import 'package:flutter/material.dart';
class PubScaffold extends StatefulWidget {
final Widget child;
PubScaffold({this.child});
@override
_PubScaffoldState createState() => _PubScaffoldState();
}
class _PubScaffoldState extends State<PubScaffold> {
bool draggable = false;
//靜止?fàn)顟B(tài)下的offset
Offset idleOffset = Offset(0, 0);
//本次移動(dòng)的offset
Offset moveOffset = Offset(0, 0);
//最后一次down事件的offset
Offset lastStartOffset = Offset(0, 0);
int count = 0;
final List<String> testWidgetList = [
'測(cè)試1',
'測(cè)試2',
];
testAppFun(e) {
// TODO: 你的代碼邏輯
}
// 顯示一個(gè)底部彈窗,這里是一個(gè)測(cè)試列表
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(
// 移動(dòng)開始
onPanStart: (DragStartDetails details) {
setState(() {
lastStartOffset = details.globalPosition;
draggable = true;
});
if (count <= 1) {
count++;
}
},
// 移動(dòng)中
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));
}
});
},
// 移動(dòng)結(jié)束
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庫(kù)中的Overlay組件岳守,具體使用方法如下:
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(
// 移動(dòng)開始
onPanStart: (DragStartDetails details) {
setState(() {
lastStartOffset = details.globalPosition;
draggable = true;
});
if (count <= 1) {
count++;
}
},
// 移動(dòng)中
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));
}
});
},
// 移動(dòng)結(jié)束
onPanEnd: (DragEndDetails detail) {
setState(() {
idleOffset = moveOffset * 1;
});
},
child: TestContainer(
onPress: () => showTestList(),
),
),
);
}),
);
}