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;

}
相关推荐
QQ3463481573 小时前
Flutter_02 工具准备2-2
flutter
淡写成灰3 小时前
造一个生产级 Flutter WebSocket 客户端:适配器模式 + 七大企业特性全解析
flutter
水云桐程序员5 小时前
Flutter与React Native的对比分析
flutter·react native·react.js
1001101_QIA5 小时前
android studio连接手机真机调试
flutter
莞凰16 小时前
昇腾CANN的“传音入密“:hccl仓库探秘
flutter·ui·transformer
5008418 小时前
Conv + BN + ReLU 融合:省掉两次显存读写
flutter·架构·开源·wpf·音视频
5008418 小时前
把 FlashAttention 讲清楚
flutter·electron·wpf
song50121 小时前
多卡训练加速:HCCL 集合通信实战
分布式·python·flutter·ci/cd·分类
风清云淡_A1 天前
【Flutter3.8x】flutter从入门到实战基础教程(一):新建一个flutter项目
flutter
1001101_QIA1 天前
Flutter 开发报错:Android cmdline-tools 缺失 环境排查与完整修复方案
android·flutter