Flutter 通过BottomSheetDialog实现抖音打开评论区,内容自动上推、缩放效果

一、先来看下实现的效果

  • 实现上面的效果需要解决俩个问题
    • 当列表进行向下滑动到顶部的时候,继续滑动可以让弹窗向下收起来
    • 弹出上下拖动的时候,视图内容跟着上下移动、缩放大小

二、实现弹窗上下滑动的时候,动态改变内容区的位置和大小

  • 通过showModalBottomSheet显示底部对话框
dart 复制代码
showModalBottomSheet(
  context: context,
  isScrollControlled: true,
  backgroundColor: Colors.white,
  transitionAnimationController: _controller,
 
  builder: (_) {
    ///省略部分代码...
  },
);

1、那问题来了,怎么去监听对话框当前显示的高度呢?

可以发现showModalBottomSheet有一个transitionAnimationController参数,这个就是对话框显示的动画控制器了值为[0,1],当全部显示是为1。

那么当将弹窗设为固定高度时,就可以通过这个值进行计算了

  • 假设我们顶部留的最小空间为:宽度 = 屏幕宽度,高度 = 屏幕宽度 / (16 / 9),那么对话框的高度就等与 屏幕高度 - 顶部高度
dart 复制代码
///屏幕宽度
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;

2、监听对话框高度改变

dart 复制代码
@override
void initState() {
  super.initState();
  _controller = BottomSheet.createAnimationController(this);
  _controller.addListener(() {
    final value = _controller.value * bottomSheetHeight;
    ///更新UI
    _bottomSheetController.sink.add(value);
  });
}

@override
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',
              ),
            );
          },
        ),
      ],
    ),
  );
}

通过上面这样处理,内容区的上移和缩小就已经实现了

三、弹窗内容向下滑动,当滑动到顶继续向下滑动时,可以让对话框继续向下滑动(不打断此次触摸事件)

1、在向下滑动到顶,继续向下的时候,动态改变弹窗内部的高度来达到弹窗下拉的效果,这里本来是想通过改变transitionAnimationController.value的值来改变弹窗的高度,但是实际中发现或的效果不理想,不知道为什么

dart 复制代码
@override
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,
                ),
              ),
            ),
          ],
        ),
      );
    },
  );
}

2、解决原理:

  • 使用Listener包裹底部可滚动组件,然后监听用户的滑动,当滑动到了最顶部且继续向下滑动就将SingleChildScrollViewphysics设置为不可滚动
  • 同时改变内容的高度,同时也要改变transitionAnimationController.value的值这样内容区才会跟着移动,缩放
  • 最后在触摸结束的时候进行判断是需要收起弹窗还是关闭弹窗
相关推荐
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
Speed1237 小时前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间7 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭7 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone8 小时前
从flutter源码看其渲染机制
android·flutter
ALLIN1 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei1 天前
Flutter 国际化
flutter
Dabei1 天前
Flutter MQTT 通信文档
flutter
Dabei1 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉1 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter