【问题标题】:Flutter Web ShowMenu Does Not Move With Screen ResizeFlutter Web ShowMenu 不随屏幕调整大小而移动
【发布时间】:2022-01-11 03:42:01
【问题描述】:

棘手的问题,希望有人有好的想法。

我在颤动的文本按钮上调用showMenu(...),效果很好。 但是,有时在屏幕调整大小期间,菜单会卡在屏幕上的某个位置(远离其预期位置)。有时它会跟随其锚定位置。很奇怪,我也注意到下拉菜单的这种行为。

这是我的示例代码。我想始终随屏幕移动菜单,或者在最坏的情况下,在屏幕调整大小事件中隐藏菜单。任何关于如何做的想法都会很棒!

class ZHomeMenuBar extends StatefulWidget {
  const ZHomeMenuBar({Key? key}) : super(key: key);

  @override
  State<ZHomeMenuBar> createState() => _ZHomeMenuBarState();
}

class _ZHomeMenuBarState extends State<ZHomeMenuBar> {
  final GlobalKey _mesKey = GlobalKey();
  final GlobalKey _accKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    final zuser = Provider.of<ZUser?>(context);
    return Container(
      height: 66,
      decoration: BoxDecoration(color: context.backgroundColor),
      padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Row(
        children: [
          Text(zuser == null ? "" : zuser.displayName ?? ""),
          const Spacer(),
          ZTextButton(text: "Portfolio", onPressed: () => {}),
          context.sh,
          ZTextButton(
              key: _mesKey,
              text: "Messages",
              onPressed: () {
                _showMessage(context);
              }),
          context.sh,
          ZTextButton(key: _accKey, text: "Account", onPressed: () {}),
          context.sh,
        ],
      ),
    );
  }

  _showMessage(context) {
    final RenderBox renderBox =
        _mesKey.currentContext?.findRenderObject() as RenderBox;
    final Size size = renderBox.size;
    final Offset offset = renderBox.localToGlobal(Offset.zero);

    showMenu(
        context: context,
        position: RelativeRect.fromLTRB(offset.dx, offset.dy + size.height,
            offset.dx + size.width, offset.dy + size.height),
        items: [
          PopupMenuItem<String>(child: const Text('menu option 1'), value: '1'),
          PopupMenuItem<String>(child: const Text('menu option 2'), value: '2'),
          PopupMenuItem<String>(child: const Text('menu option 3'), value: '3'),
        ]);
  }
}

【问题讨论】:

    标签: flutter flutter-web


    【解决方案1】:

    为了在调整屏幕大小时隐藏菜单,可以使用dart:htmlwindow 对象将事件监听器添加到浏览器的resize 并弹出菜单的上下文。

    这是您的 ZHomeMenuBar 小部件的更新代码:

    class ZHomeMenuBar extends StatefulWidget {
      ...
    }
    
    class _ZHomeMenuBarState extends State<ZHomeMenuBar> {
      ...
    
      Timer? timer;
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
    
        if (kIsWeb) {
    
          final int debounceDuration = 100;
    
          html.window.addEventListener("resize", (_) {
            final modalRoute = ModalRoute.of(context);
    
            if (modalRoute == null) {
              return;
            }
    
            if (!modalRoute.isCurrent) {
              Navigator.pop(context);
    
              if (timer != null && timer!.isActive) {
                timer!.cancel();
              }
    
              timer = Timer(Duration(milliseconds: 100), () {
                _showMessage(context);
              });
            }
          });
    
        }
    
      }
    
      @override
      void dispose() {
        super.dispose();
        timer?.cancel();
      }
    
      @override
      Widget build(BuildContext context) {
        ...
      }
    
      _showMessage(context) {
        ...
      }
    }
    

    那么didChangeDependencies 方法中的代码的作用是:

    1. 窗口监听调整大小事件。
    2. 获取上下文的ModalRoute
    3. 如果ModalRoute 为空,则该方法返回
    4. 检查ModalRouteisCurrent 属性。如果为 false,则意味着 ModalRoute 不是导航器上最顶层的路线,这意味着屏幕上有一个对话框。
    5. 所以如果isCurrent 为假,我们会弹出上下文(这会删除对话框),并使用debounceDuration 设置一个短计时器,然后显示对话框。这将使用新重新计算的尺寸并在正确的位置显示对话框。
    6. 如果计时器在另一个计时器运行时处于活动状态,我们将取消前一个计时器并将新计时器分配给timer 变量。

    您可以根据需要更新debounceDuration

    【讨论】:

    • 我真的很感谢这里的努力,我明白你想要什么。我想知道在颤振中是否有更平滑的方式来做到这一点,但也许没有..
    • 另外,我们如何在这里获取上下文?
    • didChangeDependencies 可以访问上下文。见api.flutter.dev/flutter/widgets/State/initState.html
    【解决方案2】:

    我只用了几个月的 Flutter,所以我的方法可能是完全错误的,但是在 Flutter 中定位任何东西时我会做两件事。

    1. 我总是根据数学表达式调整小部件的大小。例如,我有这个帮助类,我在 stackoverflow 的其他地方找到了它,它可以让我获得屏幕的宽度和高度。要使用它,您只需将以下内容放在任何小部件的构建方法的开头:
    SizeConfig.init(context); 
    

    再一次,我不能把这个助手类归功于我,我是从这里得到的。如果我能找到它,我会链接它。

    import 'package:flutter/material.dart';
    
    class SizeConfig {
      static late MediaQueryData _mediaQueryData;
      static double screenWidth = 0;
      static double screenHeight = 0;
      static double blockSizeHorizontal = 0;
      static double blockSizeVertical = 0;
      static double _safeAreaHorizontal = 0;
      static double _safeAreaVertical = 0;
      static double safeBlockHorizontal = 0;
      static double safeBlockVertical = 0;
    
      void init(BuildContext context){
        _mediaQueryData = MediaQuery.of(context);
        screenWidth = _mediaQueryData.size.width;
        screenHeight = _mediaQueryData.size.height;
        blockSizeHorizontal = screenWidth/100;
        blockSizeVertical = screenHeight/100;
        _safeAreaHorizontal = _mediaQueryData.padding.left + _mediaQueryData.padding.right;
        _safeAreaVertical = _mediaQueryData.padding.top + _mediaQueryData.padding.bottom + kToolbarHeight;
        safeBlockHorizontal = (screenWidth - _safeAreaHorizontal)/100;
        safeBlockVertical = (screenHeight - _safeAreaVertical)/100;
      }
    }
    

    如果您根据数学表达式使用此类和大小,则每次调整窗口大小时都会重新评估该表达式。因此,例如,这将始终将高度设置为安全可用高度的 1/4(安全可用高度占标准应用栏)。调整窗口大小时,会重新计算此值。此方法适用于小部件尺寸缩放

    height: SizeConfig.safeBlockVertical * 25,
    
    1. 对于定位,然后我使用容器、行、列、填充、中心等。我从来没有发现自己需要使用“定位”或偏移,这来自我的下一点......

    2. 如果页面会很忙,我总是以 Stack 小部件作为基础,这提供了灵活性,因为通过根据数学表达式调整所有内容的大小,我可以将两个小部件直接相邻放置,但位于不同的“层” "的堆栈,没有人能说出来。

    3. 奖励:如果您遇到需要在特定条件下完全修改小部件或其大小的实例,尽管在很多情况下这是不好的做法,但对于简单的条件渲染,可以使用通用小部件返回函数真是一件好事。或者,考虑您的特定参数并返回不同高度或宽度的通用函数可能很有用。

    以这个布局代码为例。根据你是在桌面平台还是在移动平台上,我会以不同的方式装饰我的路线。我正在为此使用 vrouter 包...

      VNesterBase(
        widgetBuilder: (child) => getValueByPlatform(
          desktop: DesktopLayout(
            key: UniqueKey(),
            child: child,
          ),
          fallback: BaseLayout(
            key: UniqueKey(),
            child: child,
          ),
        ),
        nestedRoutes: [] //nested routes
    

    编辑:我通过删除按钮并用文本小部件替换它们来测试你的类,当我调整窗口大小时它们似乎可以正确移动。

    【讨论】:

      【解决方案3】:

      你可以使用WidgetsBindingObserver mixin,它可以用来监听Widget的resize,这样你就可以pop显示的菜单了。这适用于所有可用平台。

      示例代码:

      import 'package:flutter/material.dart';
      
      class TestView extends StatefulWidget {
        const TestView({Key? key}) : super(key: key);
      
        @override
        _TestViewState createState() => _TestViewState();
      }
      
      class _TestViewState extends State<TestView> with WidgetsBindingObserver {
        late Size _lastSize;
      
        @override
        void initState() {
          super.initState();
          _lastSize = WidgetsBinding.instance!.window.physicalSize;
          WidgetsBinding.instance!.addObserver(this);
        }
      
        @override
        void dispose() {
          WidgetsBinding.instance!.removeObserver(this);
          super.dispose();
        }
      
        @override
        void didChangeMetrics() {
          setState(() {
            _lastSize = WidgetsBinding.instance!.window.physicalSize;
          });
          Navigator.of(context).popUntil((route) {
            return route is MaterialPageRoute;
          });
        }
      
        void _select(String choice) {}
      
        @override
        Widget build(BuildContext context) {
          List<String> choices = ["1", "2", "3"];
          return Scaffold(
            appBar: AppBar(
              title: const Text('test'),
              actions: <Widget>[
                PopupMenuButton(
                  onSelected: _select,
                  padding: EdgeInsets.zero,
                  // initialValue: choices[_selection],
                  itemBuilder: (BuildContext context) {
                    return choices.map((String choice) {
                      return PopupMenuItem<String>(
                        value: choice,
                        child: Text(choice),
                      );
                    }).toList();
                  },
                )
              ],
            ),
            body: Center(
              child: Column(
                children: [
                  Text('Current size: $_lastSize'),
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: DropdownButton<ThemeMode>(
                      value: null,
                      onChanged: (tm) {
                        print(_lastSize);
                      },
                      items: const [
                        DropdownMenuItem(
                          value: ThemeMode.system,
                          child: Text('System Theme'),
                        ),
                        DropdownMenuItem(
                          value: ThemeMode.light,
                          child: Text('Light Theme'),
                        ),
                        DropdownMenuItem(
                          value: ThemeMode.dark,
                          child: Text('Dark Theme'),
                        )
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        }
      }
      
      

      代替:

      Navigator.of(context).popUntil((route) {
        return route is MaterialPageRoute;
      });
      

      你也可以使用Navigator.of(context).popUntil(ModalRoute.withName('YourPageName'));

      如果您使用命名路由。

      【讨论】:

        猜你喜欢
        • 2020-04-30
        • 1970-01-01
        • 2020-07-18
        • 1970-01-01
        • 2020-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多