赞
踩
场景
我在客服系统中定义个列表widget和底层输入widget,但是在软键盘的弹出收起的时候,发现列表widget 并没有被重新绘制,这导致列表中的部分信息被遮挡,
1、Scaffold
首先想到的 Scaffold 的 resizeToAvoidBottomInset 属性。
在 Flutter 中 Scaffold 默认情况下 resizeToAvoidBottomInset 为 true,当 resizeToAvoidBottomInset 为 true 时,Scaffold 内部会将 mediaQuery.viewInsets.bottom 参与到 BoxConstraints 的大小计算,也就是键盘弹起时调整了内部的 bottom 位置来迎合键盘。这时候键盘已经收起,mediaQuery.viewInsets.bottom 应该更新为 0 ,那为何界面没有产生应有的更新呢?
MediaQuery
MediaQuery是建立媒体查询解析给定数据的子树。
使用时是获取到MediaQueryData。所以只介绍MediaQueryData的属性
“可以被系统显示的区域,通常是和设备的键盘等相关,当键盘弹出时 viewInsets.bottom 对应的就是键盘的顶部。”
那上面的 bug 看起来可能就是 Scaffold 的 viewInsets.bottom 在键盘收起来时没有正常重置。
原因
Navigator 的相关逻辑,我们常用的 Navigator 其实是一个 StatefulWidget,当 MaterialApp 被更新时,可以看到在 NavigatorState 的 didUpdateWidget 回调中会调用 _history 里所有路由的 changedExternalState() 方法。
@override
void didUpdateWidget(Navigator oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.observers != widget.observers) {
for (NavigatorObserver observer in oldWidget.observers)
observer._navigator = null;
for (NavigatorObserver observer in widget.observers) {
assert(observer.navigator == null);
observer._navigator = this;
}
}
for (Route<dynamic> route in _history)
route.changedExternalState();
}
而 changedExternalState 执行后会调用 _forceRebuildPage 将路由里的 _page 清空,这样自然下次 Route 在 build 时触发的 PageRoute 重新 builder 方法。
@override
void changedExternalState() {
super.changedExternalState();
if (_scopeKey.currentState != null)
_scopeKey.currentState._forceRebuildPage();
}
·····
void _forceRebuildPage() {
setState(() {
_page = null;
});
}
这个 bug 首先是因为不规范使用了 MediaQueryData.fromWindow(WidgetsBinding.instance.window) ,之后又恰好在有键盘的页面打开后触发了 MaterialApp 的更新,导致了 PageRoute 重新 builder, 使得没有键盘的 Scaffold 使用了弹出键盘的 viewInsets.bottom。
所以这里只需要将 MediaQueryData.fromWindow 换成 MediaQuery.of(context) 就可以解决问题,而当在没有 context 或者需要直接使用 MediaQueryData.fromWindow 时,那一定要搭配上 WidgetsBindingObserver.didChangeMetrics 配合更新。
感谢郭神!!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。