Flutter BottomSheet 拖动分两段展示

本文介绍了如何使用GestureDetector和Drag在Flutter中实现一个可自定义高度的BottomSheetDialog,通过设定最大和最小高度限制,实现动态展示和拖动功能。
摘要由CSDN通过智能技术生成

第一段

20231229-175102.jpeg

第二段

20231229-175107.jpeg

实现思路

通过 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;

}
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter提供了一个名为`BottomSheet`的小部件,用于在页面底部显示一个可滚动的内容面板。以下是一个简单的示例,演示如何使用`BottomSheet`小部件: ```dart void _showBottomSheet(BuildContext context) { showModalBottomSheet( context: context, builder: (BuildContext context) { return Container( height: 200, color: Colors.grey[200], child: Center( child: Text( 'This is a bottom sheet', style: TextStyle(fontSize: 24), ), ), ); }, ); } ``` 在上面的示例中,我们使用`showModalBottomSheet`方法显示一个带有文本的容器。您可以根据需要自定义容器的内容和样式。 您还可以使用`BottomSheet`小部件自定义底部面板并将其附加到页面的底部。以下是一个示例: ```dart void _showBottomSheet(BuildContext context) { showModalBottomSheet( context: context, builder: (BuildContext context) { return BottomSheet( onClosing: () {}, builder: (BuildContext context) { return Container( height: 200, color: Colors.grey[200], child: Center( child: Text( 'This is a custom bottom sheet', style: TextStyle(fontSize: 24), ), ), ); }, ); }, ); } ``` 在上面的示例中,我们使用`BottomSheet`小部件创建一个带有文本的容器,并将其附加到页面的底部。`onClosing`回调可用于在用户关闭底部面板时执行操作。您可以根据需要自定义容器的内容和样式。 需要注意的是,`showModalBottomSheet`方法返回一个`Future`对象,该对象在底部面板被关闭时解析。可以使用`Navigator.pop`方法从底部面板中返回数据。例如: ```dart void _showBottomSheet(BuildContext context) async { var result = await showModalBottomSheet( context: context, builder: (BuildContext context) { return Container( height: 200, color: Colors.grey[200], child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Select an option', style: TextStyle(fontSize: 24), ), SizedBox(height: 16), ElevatedButton( onPressed: () => Navigator.of(context).pop('Option 1'), child: Text('Option 1'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop('Option 2'), child: Text('Option 2'), ), ], ), ), ); }, ); print(result); } ``` 在上面的示例中,我们在底部面板中显示两个按钮,并使用`Navigator.pop`方法将选定的选项返回给调用方。返回的选项将解析为`result`变量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值