需求
- 支持底部弹出对话框。
- 支持手势滑动关闭。
- 支持在widget中嵌入引用。
- 支持底部弹出框弹出后不影响其他操作。
- 支持弹出框中内容固定头部和下面列表时,支持触摸头部并在列表不在头部的时候支持滑动关闭
简述
通过上面的需求可知,就是在界面中可以支持底部弹出一个弹出框,但是又不影响除了这个弹出框外其他操作,同时支持手势滑动关闭。想到这里我们其实会想到一个控件DraggableScrollableSheet,Flutter提供一种可以支持在widget中引入的底部弹出布局,同时不影响其他的操作,所以我就使用这个控件测试了下,发现一个问题,就是当底部弹出框内容为上面有个头部,底部是个列表时,每次都需要列表滑动到顶部的时候才可以手势滑动关闭,所以需要支持触摸顶部布局也支持手势滑动关闭,所以在此控件上做一个修改。
效果
代码如下
`import 'package:flutter/material.dart';
/// 底部弹出Widget
/// 1、支持手势下拉关闭
/// 2、支持动画弹出收起
/// 3、支持弹出无法关闭
class DragBottomSheetWidget extends StatefulWidget {
DragBottomSheetWidget({
required Key key,
required this.builder,
this.duration,
this.childHeightRatio = 0.8,
this.onStateChange,
}) : super(key: key);
Function(bool)? onStateChange;
final double childHeightRatio;
final Duration? duration;
final ScrollableWidgetBuilder builder;
@override
State<DragBottomSheetWidget> createState() => DragBottomSheetWidgetState();
}
class DragBottomSheetWidgetState extends State<DragBottomSheetWidget>
with TickerProviderStateMixin {
final controller = DraggableScrollableController();
//是否可以关闭
bool isCanClose = true;
//是否展开
bool isExpand = false;
double verticalDistance = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
Future show({bool isCanClose = true}) {
return controller
.animateTo(1,
duration: widget.duration ?? const Duration(milliseconds: 200),
curve: Curves.linear)
.then((value) {
if (!isCanClose) {
setState(() {
this.isCanClose = isCanClose;
});
}
});
}
void hide() {
controller.animateTo(
0,
duration: widget.duration ?? const Duration(milliseconds: 200),
curve: Curves.linear,
);
}
void _dragJumpTo(double y) {
var size = y / MediaQuery.of(context).size.height;
controller.jumpTo(widget.childHeightRatio - size);
}
void _dragEndChange() {
controller.size >= widget.childHeightRatio / 2
? controller.animateTo(
1,
duration: widget.duration ?? const Duration(milliseconds: 200),
curve: Curves.linear,
)
: hide();
}
@override
Widget build(BuildContext context) {
return NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
if (notification.extent == widget.childHeightRatio) {
if (!isExpand) {
isExpand = true;
widget.onStateChange?.call(true);
}
} else if (notification.extent < 0.00001) {
if (isExpand) {
isExpand = false;
widget.onStateChange?.call(false);
}
}
return true;
},
child: DraggableScrollableSheet(
initialChildSize: isCanClose && !isExpand ? 0 : widget.childHeightRatio,
minChildSize: isCanClose ? 0 : widget.childHeightRatio,
maxChildSize: widget.childHeightRatio,
expand: true,
snap: true,
controller: controller,
builder: (BuildContext context, ScrollController scrollController) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragDown: (details) {
verticalDistance = 0;
},
onVerticalDragUpdate: (details) {
verticalDistance += details.delta.dy;
_dragJumpTo(verticalDistance);
},
onVerticalDragEnd: (details) {
_dragEndChange();
},
child: widget.builder.call(context, scrollController),
);
},
),
);
}
}
使用
import 'package:flutter/material.dart';
import 'package:flutter_xy/widgets/xy_app_bar.dart';
import 'package:flutter_xy/xydemo/darg/drag_bottom_sheet_widget.dart';
class DragBottomSheetPage extends StatefulWidget {
const DragBottomSheetPage({super.key});
@override
State<DragBottomSheetPage> createState() => _DragBottomSheetPageState();
}
class _DragBottomSheetPageState extends State<DragBottomSheetPage> {
final GlobalKey<DragBottomSheetWidgetState> bottomSheetKey =
GlobalKey<DragBottomSheetWidgetState>();
bool isExpand = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple.withAlpha(50),
appBar: XYAppBar(
title: "底部弹出布局",
backgroundColor: Colors.transparent,
titleColor: Colors.white,
onBack: () {
Navigator.pop(context);
},
),
body: Stack(
children: [
Positioned(
top: 0,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
InkWell(
onTap: () {
if (isExpand) {
bottomSheetKey.currentState?.hide();
} else {
bottomSheetKey.currentState?.show();
}
},
child: Container(
width: MediaQuery.of(context).size.width - 60,
alignment: Alignment.center,
height: 50,
margin: const EdgeInsets.symmetric(horizontal: 30),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.deepPurple,
),
child: Text(
isExpand ? "收起" : "弹出",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
const SizedBox(height: 20),
],
)),
Positioned(
child: DragBottomSheetWidget(
key: bottomSheetKey,
onStateChange: (isExpand) {
setState(() {
this.isExpand = isExpand;
});
},
builder:
(BuildContext context, ScrollController scrollController) {
return Container(
alignment: Alignment.topLeft,
child: Stack(
children: [
Container(
height: 50,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16)),
color: Colors.yellow,
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 50),
child: ListView.builder(
itemCount: 500,
controller: scrollController,
itemBuilder: (context, index) {
return index % 2 == 0
? Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.red,
)
: Container(
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.blue,
);
},
),
),
),
],
),
);
},
),
),
],
),
);
}
}
完整代码见:https://github.com/yixiaolunhui/flutter_xy