既然是基于系统的BottomSheet ,不妨来看看sdk的实现方式,正常来讲,显示一个BottomSheet,可以通过showBottomSheet 来触发,或者给Scaffold配置bottomSheet属性,查看源码可以看到Scaffold.of(context).showBottomSheet,内部是创建了一个_StandardBottomSheet,继续追踪发现Widget其实是通过AnimatedBuilder来实现内容高度的扩展,其内部维护了一个BottomSheet。
简单阅读下BottomSheet源码,重点就在于 GestureDetector 的垂直方向上的手势回调 onVerticalDragUpdate 、以及onVerticalDragEnd,拖动位置更新、惯性滑动以及销毁,核心都在这了。
保留这一份默认效果,对于想使用默认效果的同学,不做任何额外配置即可。
开启三段式,我们还需要配置一个约束条件,即BottomSheet的最大高度和最小高度 BoxConstraints:
var peekThreshold = enableHalf
? min(_childHeight / 2, constraints.minHeight) / _childHeight
: constraints.minHeight / _childHeight;
当拖拽结束时,如果拖拽偏移量小于此阀值,则恢复状态,这里有个麻烦的点是需要根据用户拖拽方向来判断,是向上还是向下拖动。
方向判断可以在 _handleDragStart 回调时记录初始偏移量startY,_handleDragEnd 时计算开始和结束的差值
/// 偏移量
var offset = updateY-startY ;
/// 当前动画值
var value = widget.animationController!.value;
late double toValue;
late BottomSheetBehavior mode;
offset<0 为向上滑动,反之 向下滑动。接下来需要根据滚动阀值来更新BottomSheet状态。
if (value >= _maxThreshold) {
// 处于Expand状态,恢复
toValue = _maxThreshold;
mode = BottomSheetBehavior.EXPANDED;
} else if (value > _halfThreshold && enableHalf) {
// 处于Half,恢复
toValue = _halfThreshold;
mode = BottomSheetBehavior.HALF;
} else {
toValue = peekThreshold;
mode = BottomSheetBehavior.PEEK;
}
if (value > _halfThreshold) {
// 处于Expand状态,恢复
toValue = _maxThreshold;
mode = BottomSheetBehavior.EXPANDED;
} else if (value > peekThreshold && enableHalf) {
// 处于Half,恢复
toValue = _halfThreshold;
mode = BottomSheetBehavior.HALF;
} else {
toValue = peekThreshold;
mode = BottomSheetBehavior.PEEK;
}
if (value > _halfThreshold) {
toValue = _maxThreshold;
mode = BottomSheetBehavior.EXPANDED;
} else if (value > peekThreshold) {
toValue = enableHalf ? _halfThreshold : _maxThreshold;
mode = enableHalf ? BottomSheetBehavior.HALF : BottomSheetBehavior.EXPANDED;
} else {
toValue = peekThreshold;
mode = BottomSheetBehavior.PEEK;
}
if (value > _halfThreshold) {
toValue = enableHalf ? _halfThreshold : peekThreshold;
mode = enableHalf ? BottomSheetBehavior.HALF : BottomSheetBehavior.PEEK;
} else {
toValue = peekThreshold;
mode = BottomSheetBehavior.PEEK;
}
以上,我们获取到了开始讲到的AnimatedBuilder的 动画值以及变化量,在**_handleDragEnd**中可以通过animateTo平滑的过渡BottomSheet状态
/// 以动画的形式fly
void animateTo(double to) {
widget.animationController!.animateTo(
to,
curve: Curves.linearToEaseOut,
duration: animateDuration,
);
}
Future.delayed(animateDuration, () => widget.onBehaviorChanged?.call(mode));
至此,既保留了flutter默认的BottomSheet效果,又扩展了三段式,当然,调用方式和系统BottomSheet一模一样,另外还可以像普通Widget一样来使用哦,来看看最终的效果吧
项目效果
Demo