• Flutter简易弹窗


    高温限电,疫情防控,一波未平,一波又起。
    学习是不可能学习的,只能在居家摸鱼才能勉强维持生活这样子。

    Flutter中有集成的弹窗方法,大致是这样:

      void showPopup() {
        showModalBottomSheet(
            context: context,
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
            builder: (BuildContext context) {
              return Container(
                color: Colors.amber,
                child: Column(
                  children: [
                    ElevatedButton(onPressed: () {}, child: Text("1")),
                    ElevatedButton(onPressed: () {}, child: Text("2")),
                  ],
                ),
              );
            });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    效果大致如下:
    showModalBottomSheet

    就是使用showModalBottomSheet或类似的API,但相对来说可定制的参数较少,比如较为重要的位置是难以控制的,而且也不美观。
    当然,不自由的组件很难美观起来。

    所以想办法自定义一下。
    自定义也简单,看看showModalBottomSheet的源码,依葫芦画瓢改改就行,大多自定义API都可以这么干。

    而showModalBottomSheet的源码是这样的:

    Future<T?> showModalBottomSheet<T>({
      required BuildContext context,
      required WidgetBuilder builder,
      Color? backgroundColor,
      double? elevation,
      ShapeBorder? shape,
      Clip? clipBehavior,
      BoxConstraints? constraints,
      Color? barrierColor,
      bool isScrollControlled = false,
      bool useRootNavigator = false,
      bool isDismissible = true,
      bool enableDrag = true,
      RouteSettings? routeSettings,
      AnimationController? transitionAnimationController,
      Offset? anchorPoint,
    }) {
      assert(context != null);
      assert(builder != null);
      assert(isScrollControlled != null);
      assert(useRootNavigator != null);
      assert(isDismissible != null);
      assert(enableDrag != null);
      assert(debugCheckHasMediaQuery(context));
      assert(debugCheckHasMaterialLocalizations(context));
    
      final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
      return navigator.push(_ModalBottomSheetRoute<T>(
        builder: builder,
        capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
        isScrollControlled: isScrollControlled,
        barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
        backgroundColor: backgroundColor,
        elevation: elevation,
        shape: shape,
        clipBehavior: clipBehavior,
        constraints: constraints,
        isDismissible: isDismissible,
        modalBarrierColor: barrierColor,
        enableDrag: enableDrag,
        settings: routeSettings,
        transitionAnimationController: transitionAnimationController,
        anchorPoint: anchorPoint,
      ));
    }
    
    • 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

    一下就明白了,弹窗是弹出另一页,自然得另有路由:

    navigator.push(_ModalBottomSheetRoute);
    
    • 1

    接下来抄抄_ModalBottomSheetRoute就行。

    class PopupFreeWindow extends PopupRoute {
      ///子组件
      final Widget child;
      ///切换动画时长,必要属性
      final Duration duration;
      ///间隔,用于微调位置
      final EdgeInsets margin;
      ///分布,用于控制大体位置 
      final Alignment alignment;
      ///外围遮罩背景色
      Color? outerBackgroudColor;
    
      ///子控件具体宽度
      double width;
      ///子控件具体高度
      double height;
      ///宽度比例
      double widthFactor;
      ///高度比例 
      double heightFactor;
      ///是否点击外围收起弹窗
      bool dismissable;
    
      PopupFreeWindow(
          {required this.child,
          this.duration = const Duration(milliseconds: 300),
          this.alignment = Alignment.bottomCenter,
          this.margin =
              const EdgeInsets.only(bottom: kBottomNavigationBarHeight * 1.5),
          this.widthFactor = 0.95,
          this.heightFactor = 0.3,
          this.width = 0,
          this.height = 0,
          this.dismissable = true});
    
      @override
      Color? get barrierColor =>
          outerBackgroudColor ?? Colors.black.withOpacity(0.3);
    
      @override
      bool get barrierDismissible => dismissable;
    
      @override
      String? get barrierLabel => null;
    
      @override
      Duration get transitionDuration => duration;
    
      @override
      Widget buildPage(BuildContext context, Animation<double> animation,
          Animation<double> secondaryAnimation) {
        Widget? content;
        if (width > 0 && height >= 0) {
          content = Container(
            alignment: alignment,
            margin: margin,
            child: Container(
              child: child,
              width: width,
              height: height,
            ),
          );
        } else {
          content = FractionallySizedBox(
              widthFactor: widthFactor,
              heightFactor: heightFactor,
              child: Container(
                child: child,
                margin: margin,
              ),
              alignment: alignment);
        }
        return FadeTransition(
            opacity: animation,
            child: SafeArea(
              child: content,
            ));
      }
    
    }
    
    • 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

    测试代码:

    void showbottom() {
        final size = MediaQuery.of(context).size;
        final width = size.width;
        final height = size.height;
        print("screen w=$width,h=$height");
        Navigator.of(context).push(PopupFreeWindow(
          // widthFactor: 0.95,
          // heightFactor: 0.4,
          height: 200,
          width: width - 30,
          child: ChatBubble(
            direction: ArrowDirection.bottom,
            arrowWidth: 30,
            arrowHeight: 20,
            conicWeight: 4.5,
            child: GridMenu(),
          ),
        ));
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    根据真实的屏幕大小,来定制弹窗的大小,默认的Alignment为底部,可以调整,默认底部的间隔为状态栏的1.5倍,可以调整,这里这么写是为了结合上一篇博客中的ChatBubble,实现底部悬浮菜单的效果。
    实际上都可以自行定义。
    效果见下图。
    pushRoute
    以上。
    (继续摸鱼去了~)

  • 相关阅读:
    团建游戏---大生意
    职场新手必备的5款办公软件,特别是第五个百试百用
    pmp项目管理考试是什么?适合哪些人学?
    [架构设计] 结构型模型
    面试阿里,倒在了第4轮后,才幡然醒悟——论系统学习的重要性
    索引的基本知识
    网络安全保险行业面临的挑战与变革
    2023年10月4日
    AspNetCore7.0源码解读之UseMiddleware
    kubenetes-pod高可用
  • 原文地址:https://blog.csdn.net/ifmylove2011/article/details/126757242