Flutter BottomSheet 拖动分两段展示

第一段

第二段

实现思路

通过 GestureDetector 的 Drag 方法,动态改变Dialog的高度,通过设置一个最大高度和最小高度分成两层进行展示

实现

常用的展示BottomSheet的方法为 showModalBottomSheet

复制代码
/// 设置最高最好以高度的比例进行设置,方便不同屏幕适配
final maxHeight = MediaQuery.of(context).size.height * maxHeightRatio;
showModalBottomSheet(
    context: context,
    builder: (ctx) => BottomSheetDialog(minHeight: minHeight, maxHeight: maxHeight),
    enableDrag: false,
    isScrollControlled: true,
    scrollControlDisabledMaxHeightRatio: maxHeightRatio,
);

因为上面我们隐藏了自带的 DragHeader ,这里自定义一个可拖动的Header

复制代码
GestureDetector(
  behavior: HitTestBehavior.opaque,
  /// 正在拖动
  onVerticalDragUpdate: (detail) {
    /// 得到当前的高度
    double dragOffset = _contentHeight - detail.delta.dy;
    if(dragOffset > maxHeight) {
      dragOffset = maxHeight;
    }
    if(dragOffset < 0) {
      dragOffset = 0;
    }
    setContentHeight(dragOffset);
  },
  /// 拖动结束
  onVerticalDragEnd: (detail) {
    print("onVerticalDragEnd");
    onDragEnd();
  },
  /// 取消拖动,当作拖动结束处理
  onVerticalDragCancel: () {
    onDragEnd();
  },
  child: Container(
    height: 55,
    alignment: Alignment.center,
    child: const Text("Drag"),
  ),
),

拖动结束处理

复制代码
void onDragEnd() {
   /// 以两段中间值为界限,回弹到指定的位置
  final mid = (maxHeight - minHeight) / 2 + minHeight;
  if(_contentHeight > mid) {
    setContentHeight(maxHeight);
  } else if(_contentHeight >= minHeight / 3 * 2) {
    setContentHeight(minHeight);
  } else {
    /// 当滑动到第一段下面位置时,就直接退出BottomSheet
    Navigator.pop(context);
  }
}

完整代码

复制代码
import 'package:ebon_smart_pay/app/core/widgets/bottom_sheet/bottom_sheet_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class BottomSheetPage extends StatelessWidget {
  const BottomSheetPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion(
      value: const SystemUiOverlayStyle(
        statusBarColor: Colors.transparent
      ),
      child: Center(
        child: FilledButton(
          onPressed: () => BottomSheetDialog.show(context, MediaQuery.of(context).size.height * 0.5, 0.75),
          child: const Text("ShowBottomSheet"),
        ),
      ),
    );
  }
}


import 'package:flutter/material.dart';

class BottomSheetDialog extends StatefulWidget {

  /// 设置高度
  final double minHeight;
  final double maxHeight;

  const BottomSheetDialog({Key? key, required this.minHeight, required this.maxHeight}) : super(key: key);

  static void show(BuildContext context, double minHeight, double maxHeightRatio) {
    final maxHeight = MediaQuery.of(context).size.height * maxHeightRatio;
    showModalBottomSheet(
        context: context,
        builder: (ctx) => BottomSheetDialog(minHeight: minHeight, maxHeight: maxHeight),
        enableDrag: false,
        isScrollControlled: true,
        scrollControlDisabledMaxHeightRatio: maxHeightRatio,
    );
  }

  @override
  State<BottomSheetDialog> createState() => _BottomSheetDialogState();
}

class _BottomSheetDialogState extends State<BottomSheetDialog> {

  double _contentHeight = 0;

  void setContentHeight(double height) => setState(() {
    _contentHeight = height;
  });

  @override
  void initState() {
    setContentHeight(widget.minHeight);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: _contentHeight,
      decoration: const BoxDecoration(
        borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
        color: Colors.white
      ),
      child: SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              onVerticalDragUpdate: (detail) {
                double dragOffset = _contentHeight - detail.delta.dy;
                if(dragOffset > maxHeight) {
                  dragOffset = maxHeight;
                }
                if(dragOffset < 0) {
                  dragOffset = 0;
                }
                setContentHeight(dragOffset);
              },
              onVerticalDragEnd: (detail) {
                print("onVerticalDragEnd");
                onDragEnd();
              },
              onVerticalDragCancel: () {
                onDragEnd();
              },
              child: Container(
                height: 55,
                alignment: Alignment.center,
                child: const Text("Drag"),
              ),
            ),
            const Divider(),
            Expanded(child: ListView.separated(
                itemBuilder: (ctx, index) => Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: Text("Item - $index"),
                ),
                separatorBuilder: (ctx, index) => const Divider(),
                itemCount: 10))
          ],
        ),
      ),
    );
  }

  void onDragEnd() {
    final mid = (maxHeight - minHeight) / 2 + minHeight;
    if(_contentHeight > mid) {
      setContentHeight(maxHeight);
    } else if(_contentHeight >= minHeight / 3 * 2) {
      setContentHeight(minHeight);
    } else {
      Navigator.pop(context);
    }
  }

  double get minHeight => widget.minHeight;
  double get maxHeight => widget.maxHeight;

}
相关推荐
2601_949575861 小时前
JSON5 跨端三方库仓颉适配全流程
前端·javascript·flutter
法的空间1 小时前
AI 编程时代,为什么 Dart 分析会拖慢大型项目的开发
android·flutter·ios
恋猫de小郭3 小时前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
好的佩奇19 小时前
Dart 之模式
android·flutter·dart
淡淡的香烟21 小时前
Flutter实现多语言切换
android·flutter·1024程序员节
事圆则缓1 天前
从零搭建一套 Flutter 架构:先跑通数据流
flutter
程序员老刘2 天前
Flutter版本选择指南:3.44稳定了11周正式转正 | 2026年10月
flutter·ai编程·客户端
chicken_h2 天前
【无标题】
flutter·打包
事圆则缓2 天前
Flutter 底层渲染引擎怎么变了:从 Skia 到 Impeller,以及迁移时的坑
flutter
码林鼠3 天前
flutter状态管理入门教程-Riverpod
开发语言·javascript·flutter