showModalBottomSheet
显示底部对话框showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
transitionAnimationController: _controller,
builder: (_) {
///省略部分代码...
},
);
可以发现
showModalBottomSheet
有一个transitionAnimationController
参数,这个就是对话框显示的动画控制器了值为[0,1],当全部显示是为1。
那么当将弹窗设为固定高度时,就可以通过这个值进行计算了
宽度 = 屏幕宽度,高度 = 屏幕宽度 / (16 / 9)
,那么对话框的高度就等与 屏幕高度 - 顶部高度
///屏幕宽度
double get screenWidth => MediaQuery.of(context).size.width;
///屏幕高度
double get screenHeight => MediaQuery.of(context).size.height;
///顶部留的高度
double get topSpaceHeight => screenWidth / (16 / 9);
///对话框高度
double get bottomSheetHeight => screenHeight - topSpaceHeight;
void initState() {
super.initState();
_controller = BottomSheet.createAnimationController(this);
_controller.addListener(() {
final value = _controller.value * bottomSheetHeight;
///更新UI
_bottomSheetController.sink.add(value);
});
}
Widget build(BuildContext context) {
final bottom = MediaQuery.of(context).padding.bottom;
return ColoredBox(
color: Colors.black,
child: Stack(
children: [
StreamBuilder<double>(
stream: _bottomSheetController.stream,
initialData: 0,
builder: (_, snapshot) {
return Container(
height: screenHeight - snapshot.data!,
alignment: Alignment.center,
child: Image.network(
'https://5b0988e595225.cdn.sohucs.com/images/20200112/75b4a498fdaa48c7813419c2d4bac477.jpeg',
),
);
},
),
],
),
);
}
通过上面这样处理,内容区的上移和缩小就已经实现了
Widget build(BuildContext context) {
return StreamBuilder<double>(
stream: _dragController.stream,
initialData: widget.height,
builder: (context, snapshot) {
return AnimatedContainer(
height: snapshot.data ?? widget.height,
duration: const Duration(milliseconds: 50),
child: Column(
children: [
widget.pinedHeader ?? const SizedBox.shrink(),
Expanded(
child: Listener(
onPointerMove: (event) {
///没有滚动到顶部不处理
if (_scrollController.offset != 0) {
return;
}
///获取滑动到顶部开始下拉的位置
_startY ??= event.position.dy;
final distance = event.position.dy - _startY!;
///弹窗滑动后剩余高度
if ((widget.height - distance) > widget.height) {
return;
}
_dragController.sink.add(widget.height - distance);
///剩余弹出高度所占百分比
final percent = 1 - distance / widget.height;
///为了处理图片大小缩放需要使用
widget.transitionAnimationController.value = percent;
},
/// 触摸事件结束 恢复可滚动
onPointerUp: (event) {
_startY = null;
if (snapshot.data! <= widget.height * 0.5) {
///下拉到了一半直接关闭
widget.transitionAnimationController.animateTo(0,
duration: const Duration(microseconds: 250));
} else {
///未到一半 恢复展示
_dragController.sink.add(widget.height);
widget.transitionAnimationController.animateTo(1,
duration: const Duration(microseconds: 250));
}
},
child: SingleChildScrollView(
controller: _scrollController,
physics: snapshot.data == widget.height
? const ClampingScrollPhysics()
: const NeverScrollableScrollPhysics(),
child: widget.child,
),
),
),
],
),
);
},
);
}
Listener
包裹底部可滚动组件,然后监听用户的滑动,当滑动到了最顶部且继续向下滑动就将SingleChildScrollView
的physics
设置为不可滚动transitionAnimationController.value
的值这样内容区才会跟着移动,缩放