当前位置:   article > 正文

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

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

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

效果

bottom_sheet.gif

代码如下
`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),
          );
        },
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

使用

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,
                                    );
                            },
                          ),
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122

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

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/267598
推荐阅读
相关标签
  

闽ICP备14008679号