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

2023-12-29

我有一个屏幕,其中包含Form with a StreamBuilder。当我加载初始数据时StreamBuilder, TextFormField按预期显示数据。
当我点击里面TextFormField,软件键盘出现,这会导致小部件重建。当键盘再次按下时,同样的情况会再次发生。

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

这是我的代码:

@override
Widget build(BuildContext context) {
  return StreamBuilder(
    stream: _bloc.inputObservable(),
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        return TextFormField(
          // ...
        );
      }
      return const Center(
        child: CircularProgressIndicator(),
      );
    },
  );
}

我该如何解决这个问题?


键盘导致重建

它使总意义 and 是期待软件键盘打开导致重建。在幕后,MediaQuery is 更新为查看插图 https://api.flutter.dev/flutter/widgets/MediaQueryData/viewInsets.html. These MediaQueryData.viewInsets确保您的 UI 知道键盘遮挡了它。抽象地说,键盘遮挡屏幕会导致窗口发生变化,大多数情况下还会导致 UI 发生变化,这需要对 UI 进行更改 - 重建。

我可以确信你正在使用Scaffold https://api.flutter.dev/flutter/material/Scaffold-class.html在您的 Flutter 应用程序中。与许多其他框架小部件一样,Scaffold小部件depends (see InheritedWidget https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)在MediaQuery https://api.flutter.dev/flutter/widgets/MediaQuery-class.html(从获取数据Window https://api.flutter.dev/flutter/foundation/BindingBase/window.html包含您的应用程序)使用MediaQuery.of(context).
See MediaQueryData https://api.flutter.dev/flutter/widgets/MediaQueryData-class.html了解更多信息。


这一切都归结为Scaffold依赖于视图插图。这使得它能够resize当这些查看插图改变。基本上,当键盘打开时,视图插图会更新,这允许脚手架在底部收缩,从而移除obscured space.

长话短说,适应调整后的视图插图的脚手架需要重建脚手架 UI。由于您的小部件必然是脚手架的子级(可能是body),当这种情况发生时,你的小部件也会被重建。

You can disable视图插入调整大小行为使用Scaffold.resizeToAvoidBottomInset https://api.flutter.dev/flutter/cupertino/CupertinoPageScaffold/resizeToAvoidBottomInset.html。但是,这不一定会停止重建,因为可能仍然依赖于MediaQuery。我将在下面解释你应该如何真正思考这个问题。

幂等构建方法

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

我的意思是幂等构建调用是当你的小部件配置没有任何信息时(在这种情况下StatelessWidgets https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html)或没有任何关于您所在州的信息(在这种情况下StatefulWidgets https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html)变化,生成的小部件树应该是严格相同。因此,您不想处理any状态在build- 它唯一的责任应该是代表当前的配置或状态。


软件键盘打开导致重建就是一个很好的例子,说明了为什么会出现这种情况。其他示例包括旋转设备、在网络上调整大小,但当您的小部件树开始变得复杂时,它实际上可以是任何东西(更多内容见下文)。

StreamBuilder重建时重新订阅

回到最初的问题:在这种情况下,你的问题是你正在接近StreamBuilder错误地。你应该not给它提供一个流重新创造了每个构建。

流构建器的工作方式是订阅初始流,然后重新订阅每当流更新时。这意味着当stream的财产StreamBuilder两者之间的小部件不同build调用时,流构建器将取消订阅第一个流并订阅第二个(新)流。

您可以在_StreamBuilderBaseState.didUpdateWidget执行 https://github.com/flutter/flutter/blob/b21e08c80160e89c800543a4b9990459bc71043d/packages/flutter/lib/src/widgets/async.dart#L114:

if (oldWidget.stream != widget.stream) {
  if (_subscription != null) {
    _unsubscribe();
    _summary = widget.afterDisconnected(_summary);
  }
  _subscribe();
}

这里显而易见的解决方案是您需要提供相同的流当您不想重新订阅时,在不同的构建调用之间进行。这又回到了幂等构建调用!


A StreamController例如将始终返回相同的流,这意味着可以安全使用stream: streamController.stream在你的StreamBuilder。基本上,所有控制器、行为主体等实现都应该以这种方式运行 - 只要你不这样做重新创造你的直播,StreamBuilder会妥善处理的!

因此,您的情况的错误功能是_bloc.inputObservable(),它每次都会创建一个新的流,而不是返回相同的流。

Notes

请注意,我说过构建调用可以“在任何时间点”发生。事实上,你can(技术上)准确控制应用程序中每个构建发生的时间。然而,普通的应用程序将非常复杂,您不可能对其进行控制,因此,您将需要幂等构建调用。
导致重建的键盘就是一个很好的例子。

如果您从高层次上考虑,这正是您想要的 - 框架及其小部件(或您创建的小部件)负责响应外部更改并在必要时进行重建。树中的叶子小部件不应该关心是否发生重建 - 它们应该可以很好地放置在任何环境中,并且框架通过相应的重建来负责对该环境的更改做出反应。

我希望我能够为您解决这个问题:)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么软件键盘会导致打开/关闭时小部件重建? 的相关文章

随机推荐