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;

}
相关推荐
problc4 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
lqj_本人12 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人15 小时前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔20 小时前
Flutter启动流程(2)
flutter
hello world smile1 天前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人1 天前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai1 天前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人1 天前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos
lqj_本人1 天前
Flutter&鸿蒙next 中的 setState 使用场景与最佳实践
flutter·华为·harmonyos
hello world smile1 天前
Flutter常用命令整理
android·flutter·移动开发·android studio·安卓