Flutter Web ShowMenu 不随屏幕调整大小而移动
Posted
技术标签:
【中文标题】Flutter Web ShowMenu 不随屏幕调整大小而移动【英文标题】:Flutter Web ShowMenu Does Not Move With Screen Resize 【发布时间】: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'),
]);
【问题讨论】:
【参考方案1】:为了在调整屏幕大小时隐藏菜单,可以使用dart:html 的window 对象将事件监听器添加到浏览器的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
方法中的代码的作用是:
-
窗口监听调整大小事件。
获取上下文的
ModalRoute
。
如果ModalRoute
为空,则该方法返回
检查ModalRoute
的isCurrent
属性。如果为 false,则意味着 ModalRoute
不是导航器上最顶层的路线,这意味着屏幕上有一个对话框。
所以如果isCurrent
为假,我们会弹出上下文(这会删除对话框),并使用debounceDuration
设置一个短计时器,然后显示对话框。这将使用新重新计算的尺寸并在正确的位置显示对话框。
如果计时器在另一个计时器运行时处于活动状态,我们将取消前一个计时器并将新计时器分配给timer
变量。
您可以根据需要更新debounceDuration
。
【讨论】:
我真的很感谢这里的努力,我明白你想要什么。我想知道在颤振中是否有更平滑的方式来做到这一点,但也许没有.. 另外,我们如何在这里获取上下文?didChangeDependencies
可以访问上下文。见api.flutter.dev/flutter/widgets/State/initState.html【参考方案2】:
我只用了几个月的 Flutter,所以我的方法可能是完全错误的,但是在 Flutter 中定位任何东西时我会做两件事。
-
我总是根据数学表达式调整小部件的大小。例如,我有这个帮助类,我在 *** 的其他地方找到了它,它可以让我获得屏幕的宽度和高度。要使用它,您只需将以下内容放在任何小部件的构建方法的开头:
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,
对于定位,然后我使用容器、行、列、填充、中心等。我从来没有发现自己需要使用“定位”或偏移,这来自我的下一点......
如果页面会很忙,我总是以 Stack 小部件作为基础,这提供了灵活性,因为通过根据数学表达式调整所有内容的大小,我可以将两个小部件直接相邻放置,但位于不同的“层” "的堆栈,没有人能说出来。
奖励:如果您遇到需要在特定条件下完全修改小部件或其大小的实例,尽管在很多情况下这是不好的做法,但对于简单的条件渲染,可以使用通用小部件返回函数真是一件好事。或者,考虑您的特定参数并返回不同高度或宽度的通用函数可能很有用。
以这个布局代码为例。根据你是在桌面平台还是在移动平台上,我会以不同的方式装饰我的路线。我正在为此使用 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'));
如果您使用命名路由。
【讨论】:
以上是关于Flutter Web ShowMenu 不随屏幕调整大小而移动的主要内容,如果未能解决你的问题,请参考以下文章