需求如图。在bottomSheet中显示一个ListView,并且需要向下滑至顶部的时候关闭该弹窗。
问题:默认情况下在滑动到顶部后就会停止滑动,bottomSheet无法继续滑动关闭。
翻阅相关源码发现bottomSheet的滑动关闭逻辑在BottomSheet这个类中,内部添加了个手势识别器,看看_handleDrag都干了啥
很明显通过滑动手势操作了AnimationController来实现的下拉动画。解题思路就是操作AnimationController来解决手势冲突导致的无法滑动关闭。
继续看如何获得AnimationController
transitionAnimationController 这不是可以从外部传吗??okok,封装一个类,把BottomSheet里的update和end方法对animationController的逻辑copy出来,尝试下。
import 'package:flutter/material.dart';
const double _minFlingVelocity = 700.0;
const double _closeProgressThreshold = 0.5;
class DragBottomSheet extends StatelessWidget {
final AnimationController animationController;
final GlobalKey _childKey = GlobalKey(debugLabel: 'DragBottomSheet child');
final VoidCallback onClosing;
final Widget child;
double get _childHeight {
final RenderBox renderBox =
_childKey.currentContext!.findRenderObject()! as RenderBox;
return renderBox.size.height;
}
DragBottomSheet({
super.key,
required this.animationController,
required this.onClosing,
required this.child,
});
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
key: _childKey,
onNotification: (ScrollNotification notification) {
_handleScroller(notification);
return true;
},
child: child);
}
void _handleScroller(ScrollNotification notification) {
if (notification is OverscrollNotification) {
if (notification.dragDetails != null) {
_handleDragUpdate(notification.dragDetails!);
}
}
if (notification is ScrollEndNotification) {
if (notification.dragDetails != null) {
_handleDragEnd(notification.dragDetails!);
}
}
}
void _handleDragUpdate(DragUpdateDetails details) {
animationController.value -= details.primaryDelta! / _childHeight;
}
void _handleDragEnd(DragEndDetails details) {
bool isClosing = false;
if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
final double flingVelocity =
-details.velocity.pixelsPerSecond.dy / _childHeight;
if (animationController.value > 0.0) {
animationController.fling(velocity: flingVelocity);
}
if (flingVelocity < 0.0) {
isClosing = true;
}
} else if (animationController.value < _closeProgressThreshold) {
if (animationController.value > 0.0) {
animationController.fling(velocity: -1.0);
}
isClosing = true;
} else {
animationController.forward();
}
if (isClosing) {
onClosing.call();
}
}
}
外部调用弹窗的地方传入自己创建的AnimationController
late final AnimationController animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 250));
Future _showBottomSheet() async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
// 传入controller
transitionAnimationController: animationController,
builder: (_) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 650),
// 传入controller
child: DragBottomSheet(
animationController: animationController,
onClosing: () {
Navigator.of(context).pop();
},
child: ListView(
children: [
for (int i = 0; i < 200; i++)
Container(
alignment: Alignment.center,
height: 50,
child: Text("第$i项"),
),
],
),
),
);
},
);
}
看看最终效果,搞定!下课!