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的值这样内容区才会跟着移动,缩放
  • 最后在触摸结束的时候进行判断是需要收起弹窗还是关闭弹窗
相关推荐
江上清风山间明月1 天前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能2 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人2 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen2 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang2 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang2 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1232 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-2 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11193 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins
机器瓦力3 天前
Flutter应用开发:对象存储管理图片
flutter