Flutter 为什么软件键盘会导致小部件在打开/关闭时重建?

Flutter 为什么软件键盘会导致小部件在打开/关闭时重建?,flutter,dart,keyboard,textfield,Flutter,Dart,Keyboard,Textfield,我有一个屏幕,其中包含一个表单和一个流生成器。当我从StreamBuilder加载初始数据时,TextFormField按预期显示数据。 当我点击TextFormField内部时,软件键盘显示出来,这会导致小部件重新构建。当键盘再次按下时,同样的情况再次发生 不幸的是,StreamBuilder再次订阅,文本框值被替换为初始值 这是我的密码: @覆盖 小部件构建(构建上下文){ 返回流生成器( 流:_bloc.inputObservable(), 生成器:(上下文,快照){ if(snapsho

我有一个屏幕,其中包含一个
表单
和一个
流生成器
。当我从
StreamBuilder
加载初始数据时,
TextFormField
按预期显示数据。
当我点击
TextFormField
内部时,软件键盘显示出来,这会导致小部件重新构建。当键盘再次按下时,同样的情况再次发生

不幸的是,
StreamBuilder
再次订阅,文本框值被替换为初始值

这是我的密码:

@覆盖
小部件构建(构建上下文){
返回流生成器(
流:_bloc.inputObservable(),
生成器:(上下文,快照){
if(snapshot.hasData){
返回TextFormField(
// ...
);
}
返回常数中心(
子对象:CircularProgressIndicator(),
);
},
);
}
如何解决此问题?

导致重建的键盘 总的来说,软件键盘打开会导致重建,这是有意义的。在幕后,
MediaQuery
。这些
MediaQueryData.viewInsets
确保您的UI知道键盘遮挡了它。抽象地说,键盘遮住屏幕会导致窗口的改变,大多数情况下会导致用户界面的改变,这就需要对用户界面进行改变——即重新构建

我可以自信地猜测,您在颤振应用程序中使用了。与许多其他框架小部件一样,
Scaffold
widgets依赖于使用
MediaQuery.of(context)

有关更多信息,请参阅


这一切归结为
Scaffold
依赖于视图插入。当这些视图插入更改时,它可以调整大小。基本上,当键盘打开时,视图插入会更新,从而允许脚手架在底部收缩,从而移除遮挡的空间

长话短说,适应调整后的视图插入的脚手架需要重建脚手架UI。而且,由于您的小部件必须是scaffold(可能是
主体
)的子部件,因此在发生这种情况时,您的小部件也会重新构建

可以使用禁用视图插入调整大小行为。但是,这并不一定会停止重建,因为可能仍然依赖于
MediaQuery
。我将在下面解释你应该如何真正思考这个问题

幂等构造方法 您应该始终以
build
方法是幂等的方式构建flatter小部件
这种范例是,构建调用可以在任何时间点发生,最高可达每秒60次(如果刷新率较高,则次数会更多)

我所说的幂等构建调用的意思是,当小部件配置(在的情况下)或状态(在的情况下)没有任何变化时,生成的小部件树应该是严格相同的。因此,您不希望处理
构建中的任何状态
——它的唯一职责应该是表示当前配置或状态


导致重建的软件键盘打开就是一个很好的例子。其他的例子还有旋转设备,在网络上调整大小,但当你的小部件树开始变得复杂时,它实际上可以是任何东西(下面将详细介绍)

StreamBuilder
重建时重新订阅 回到原来的问题:在这种情况下,您的问题是您正在错误地接近
StreamBuilder
。您应该而不是为它提供一个流,该流在每次构建时都重新创建

流生成器的工作方式是订阅初始流,然后在流更新时重新订阅。这意味着,当两个
build
调用之间的
stream
小部件的
stream
属性不同时,stream builder将取消订阅第一个流,并订阅第二个(新)流

您可以在以下列表中看到这一点:

if(oldWidget.stream!=widget.stream){
如果(_订阅!=null){
_取消订阅();
_summary=小部件。断开连接后(\u summary);
}
_订阅();
}
这里显而易见的解决方案是,当您不想重新订阅时,您需要在不同的构建调用之间提供相同的流。这可以追溯到幂等生成调用


例如,
StreamController
将始终返回相同的流,这意味着在
StreamBuilder
中使用
stream:StreamController.stream
是安全的。基本上,所有控制器、行为主体等实现都应该以这种方式运行-只要您不重新创建流,
StreamBuilder
将正确处理它

因此,您案例中的错误函数是
\u bloc.inputObservable()
,它每次都创建一个新流,而不是返回相同的流

笔记 注意,我说过构建调用可以“在任何时间点”发生。实际上,你可以(从技术上)精确地控制你的应用程序中每次构建的时间。然而,一个普通的应用程序将是如此复杂,以至于你不可能控制它,因此,你将需要幂等生成调用。
导致重建的键盘就是一个很好的例子

如果您从较高的层次来考虑,这正是您想要的——框架及其小部件(或您创建的小部件)负责响应外部更改并在必要时进行重建。树中的叶子小部件不应该关心是否发生了重建——它们应该放在任何环境中都可以,框架负责