Flutter-底部弹出框(Widget层级)

需求
  • 支持底部弹出对话框。
  • 支持手势滑动关闭。
  • 支持在widget中嵌入引用。
  • 支持底部弹出框弹出后不影响其他操作。
  • 支持弹出框中内容固定头部和下面列表时,支持触摸头部并在列表不在头部的时候支持滑动关闭
简述

通过上面的需求可知,就是在界面中可以支持底部弹出一个弹出框,但是又不影响除了这个弹出框外其他操作,同时支持手势滑动关闭。想到这里我们其实会想到一个控件DraggableScrollableSheet,Flutter提供一种可以支持在widget中引入的底部弹出布局,同时不影响其他的操作,所以我就使用这个控件测试了下,发现一个问题,就是当底部弹出框内容为上面有个头部,底部是个列表时,每次都需要列表滑动到顶部的时候才可以手势滑动关闭,所以需要支持触摸顶部布局也支持手势滑动关闭,所以在此控件上做一个修改。

效果
代码如下
`import 'package:flutter/material.dart';

/// 底部弹出Widget
/// 1、支持手势下拉关闭
/// 2、支持动画弹出收起
/// 3、支持弹出无法关闭
class DragBottomSheetWidget extends StatefulWidget {
  DragBottomSheetWidget({
    required Key key,
    required this.builder,
    this.duration,
    this.childHeightRatio = 0.8,
    this.onStateChange,
  }) : super(key: key);

  Function(bool)? onStateChange;

  final double childHeightRatio;

  final Duration? duration;

  final ScrollableWidgetBuilder builder;

  @override
  State<DragBottomSheetWidget> createState() => DragBottomSheetWidgetState();
}

class DragBottomSheetWidgetState extends State<DragBottomSheetWidget>
    with TickerProviderStateMixin {
  final controller = DraggableScrollableController();

  //是否可以关闭
  bool isCanClose = true;

  //是否展开
  bool isExpand = false;

  double verticalDistance = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  Future show({bool isCanClose = true}) {
    return controller
        .animateTo(1,
            duration: widget.duration ?? const Duration(milliseconds: 200),
            curve: Curves.linear)
        .then((value) {
      if (!isCanClose) {
        setState(() {
          this.isCanClose = isCanClose;
        });
      }
    });
  }

  void hide() {
    controller.animateTo(
      0,
      duration: widget.duration ?? const Duration(milliseconds: 200),
      curve: Curves.linear,
    );
  }

  void _dragJumpTo(double y) {
    var size = y / MediaQuery.of(context).size.height;
    controller.jumpTo(widget.childHeightRatio - size);
  }

  void _dragEndChange() {
    controller.size >= widget.childHeightRatio / 2
        ? controller.animateTo(
            1,
            duration: widget.duration ?? const Duration(milliseconds: 200),
            curve: Curves.linear,
          )
        : hide();
  }

  @override
  Widget build(BuildContext context) {
    return NotificationListener<DraggableScrollableNotification>(
      onNotification: (notification) {
        if (notification.extent == widget.childHeightRatio) {
          if (!isExpand) {
            isExpand = true;
            widget.onStateChange?.call(true);
          }
        } else if (notification.extent < 0.00001) {
          if (isExpand) {
            isExpand = false;
            widget.onStateChange?.call(false);
          }
        }
        return true;
      },
      child: DraggableScrollableSheet(
        initialChildSize: isCanClose && !isExpand ? 0 : widget.childHeightRatio,
        minChildSize: isCanClose ? 0 : widget.childHeightRatio,
        maxChildSize: widget.childHeightRatio,
        expand: true,
        snap: true,
        controller: controller,
        builder: (BuildContext context, ScrollController scrollController) {
          return GestureDetector(
            behavior: HitTestBehavior.translucent,
            onVerticalDragDown: (details) {
              verticalDistance = 0;
            },
            onVerticalDragUpdate: (details) {
              verticalDistance += details.delta.dy;
              _dragJumpTo(verticalDistance);
            },
            onVerticalDragEnd: (details) {
              _dragEndChange();
            },
            child: widget.builder.call(context, scrollController),
          );
        },
      ),
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutter_xy/widgets/xy_app_bar.dart';
import 'package:flutter_xy/xydemo/darg/drag_bottom_sheet_widget.dart';

class DragBottomSheetPage extends StatefulWidget {
  const DragBottomSheetPage({super.key});

  @override
  State<DragBottomSheetPage> createState() => _DragBottomSheetPageState();
}

class _DragBottomSheetPageState extends State<DragBottomSheetPage> {
  final GlobalKey<DragBottomSheetWidgetState> bottomSheetKey =
      GlobalKey<DragBottomSheetWidgetState>();

  bool isExpand = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.deepPurple.withAlpha(50),
      appBar: XYAppBar(
        title: "底部弹出布局",
        backgroundColor: Colors.transparent,
        titleColor: Colors.white,
        onBack: () {
          Navigator.pop(context);
        },
      ),
      body: Stack(
        children: [
          Positioned(
              top: 0,
              child: Column(
                mainAxisSize: MainAxisSize.max,
                children: [
                  InkWell(
                    onTap: () {
                      if (isExpand) {
                        bottomSheetKey.currentState?.hide();
                      } else {
                        bottomSheetKey.currentState?.show();
                      }
                    },
                    child: Container(
                      width: MediaQuery.of(context).size.width - 60,
                      alignment: Alignment.center,
                      height: 50,
                      margin: const EdgeInsets.symmetric(horizontal: 30),
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(32),
                        color: Colors.deepPurple,
                      ),
                      child: Text(
                        isExpand ? "收起" : "弹出",
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w600,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),
                ],
              )),
          Positioned(
            child: DragBottomSheetWidget(
              key: bottomSheetKey,
              onStateChange: (isExpand) {
                setState(() {
                  this.isExpand = isExpand;
                });
              },
              builder:
                  (BuildContext context, ScrollController scrollController) {
                return Container(
                  alignment: Alignment.topLeft,
                  child: Stack(
                    children: [
                      Container(
                        height: 50,
                        decoration: const BoxDecoration(
                          borderRadius: BorderRadius.only(
                              topLeft: Radius.circular(16),
                              topRight: Radius.circular(16)),
                          color: Colors.yellow,
                        ),
                      ),
                      Expanded(
                        child: Container(
                          margin: const EdgeInsets.only(top: 50),
                          child: ListView.builder(
                            itemCount: 500,
                            controller: scrollController,
                            itemBuilder: (context, index) {
                              return index % 2 == 0
                                  ? Container(
                                      height: 100,
                                      width: MediaQuery.of(context).size.width,
                                      color: Colors.red,
                                    )
                                  : Container(
                                      height: 100,
                                      width: MediaQuery.of(context).size.width,
                                      color: Colors.blue,
                                    );
                            },
                          ),
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

完整代码见:https://github.com/yixiaolunhui/flutter_xy

相关推荐
API_technology2 分钟前
电商API安全防护:JWT令牌与XSS防御实战
前端·安全·xss
yqcoder7 分钟前
Express + MongoDB 实现在筛选时间段中用户名的模糊查询
java·前端·javascript
十八朵郁金香28 分钟前
通俗易懂的DOM1级标准介绍
开发语言·前端·javascript
GDAL1 小时前
HTML 中的 Canvas 样式设置全解
javascript
m0_528723812 小时前
HTML中,title和h1标签的区别是什么?
前端·html
Dark_programmer2 小时前
html - - - - - modal弹窗出现时,页面怎么能限制滚动
前端·html
GDAL2 小时前
HTML Canvas clip 深入全面讲解
前端·javascript·canvas
禾苗种树2 小时前
在 Vue 3 中使用 ECharts 制作多 Y 轴折线图时,若希望 **Y 轴颜色自动匹配折线颜色**且无需手动干预,可以通过以下步骤实现:
前端·vue.js·echarts
GISer_Jing2 小时前
Javascript排序算法(冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序)详解
javascript·算法·排序算法
贵州数擎科技有限公司2 小时前
使用 Three.js 实现流光特效
前端·webgl