在 Flutter 开发中,底部弹出框(Bottom Sheet)是一种常见的 UI 组件,通常用于显示一些额外的操作选项或详细信息。在这篇文章中,我将介绍一个自定义的 DragBottomSheetWidget
组件,它不仅支持手势拖动关闭,还可以通过动画进行弹出和收起。
组件功能概述
DragBottomSheetWidget
是一个支持手势拖动和动画效果的底部弹出框组件。它具有以下几个主要功能:
- 手势下拉关闭:用户可以通过向下拖动来关闭底部弹出框。
- 动画弹出收起:支持平滑的动画效果,弹出或收起时更加自然。
- 弹出后无法关闭:在特定场景下,弹出框可以设置为无法通过手势关闭。
代码解析
首先,我们来看一下 DragBottomSheetWidget
的代码实现:
dart
import 'package:flutter/material.dart';
class DragBottomSheetWidget extends StatefulWidget {
const DragBottomSheetWidget({
super.key,
required this.builder,
this.duration = const Duration(milliseconds: 200),
this.childHeightRatio = 0.8,
this.onStateChange,
});
final Function(bool)? onStateChange;
final double childHeightRatio;
final Duration duration;
final ScrollableWidgetBuilder builder;
@override
State<DragBottomSheetWidget> createState() => DragBottomSheetWidgetState();
}
class DragBottomSheetWidgetState extends State<DragBottomSheetWidget> {
final DraggableScrollableController controller =
DraggableScrollableController();
final ValueNotifier<bool> isExpandNotifier = ValueNotifier<bool>(false);
double verticalDistance = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
controller.dispose();
isExpandNotifier.dispose();
super.dispose();
}
Future<void> show({bool isCanClose = true}) async {
try {
await controller.animateTo(
1,
duration: widget.duration,
curve: Curves.linear,
);
if (!isCanClose) {
isExpandNotifier.value = true;
}
} catch (e) {
print('Error animating to full size: $e');
}
}
void hide() {
controller.animateTo(
0,
duration: widget.duration,
curve: Curves.linear,
);
}
void _dragJumpTo(double y) {
final size = y / MediaQuery.sizeOf(context).height;
final jumpToValue = widget.childHeightRatio - size;
controller.jumpTo(jumpToValue.clamp(0, widget.childHeightRatio));
}
void _dragEndChange(DragEndDetails dragEndDetails) {
if (controller.size >= widget.childHeightRatio / 2) {
controller.animateTo(
1,
duration: widget.duration,
curve: Curves.linear,
);
} else {
hide();
}
}
@override
Widget build(BuildContext context) {
return NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
isExpandNotifier.value = notification.extent == widget.childHeightRatio;
widget.onStateChange?.call(isExpandNotifier.value);
return true;
},
child: ValueListenableBuilder<bool>(
valueListenable: isExpandNotifier,
builder: (context, isExpand, child) {
return DraggableScrollableSheet(
initialChildSize: !isExpand ? 0 : widget.childHeightRatio,
minChildSize: 0,
maxChildSize: widget.childHeightRatio,
expand: true,
snap: true,
controller: controller,
builder: (BuildContext context, ScrollController scrollController) {
return _DragHandler(
onDragDown: () => verticalDistance = 0,
onDragUpdate: (details) {
verticalDistance += details.delta.dy;
_dragJumpTo(verticalDistance);
},
onDragEnd: _dragEndChange,
child: widget.builder(context, scrollController),
);
},
);
},
),
);
}
}
class _DragHandler extends StatelessWidget {
const _DragHandler({
required this.onDragDown,
required this.onDragUpdate,
required this.onDragEnd,
required this.child,
});
final VoidCallback onDragDown;
final GestureDragUpdateCallback onDragUpdate;
final GestureDragEndCallback onDragEnd;
final Widget child;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragDown: (_) => onDragDown(),
onVerticalDragUpdate: onDragUpdate,
onVerticalDragEnd: onDragEnd,
child: child,
);
}
}
核心功能解读
-
控制器与状态管理 :组件内部使用了
DraggableScrollableController
来控制弹出框的显示状态,ValueNotifier<bool>
用于监听弹出框的展开与收起状态。 -
显示与隐藏 :
show
方法通过animateTo
将弹出框平滑展开至全屏,而hide
方法则将其收起至不可见状态。 -
手势控制 :通过
_dragJumpTo
方法和_dragEndChange
方法,组件可以响应用户的手势操作,决定弹出框的滑动与状态变化。 -
Builder 模式 :通过
builder
回调,开发者可以自定义弹出框中的内容,满足不同场景的需求。
使用场景
DragBottomSheetWidget
适用于需要在屏幕底部弹出一些交互式内容的场景,如表单输入、操作选项等。通过手势控制和动画效果,可以为用户提供更加流畅和直观的操作体验。
结语
自定义 DragBottomSheetWidget
组件不仅增强了 Flutter 的弹出框功能,还为用户提供了更多的交互可能性。如果你正在开发需要底部弹出框的应用,不妨尝试一下这个组件,为你的应用添加更丰富的交互体验。