在 Flutter 中推送新页面时,Navigator 堆栈上的页面会重建

Posted

技术标签:

【中文标题】在 Flutter 中推送新页面时,Navigator 堆栈上的页面会重建【英文标题】:Pages on Navigator stack rebuild when a new page is pushed in Flutter 【发布时间】:2018-10-21 22:06:30 【问题描述】:

我正在开发一个 Flutter 应用程序,它会提示一个表单来询问一些个人信息。

问题是每次发生某些事情时都会重新构建页面,例如当屏幕方向发生变化或文本字段获得焦点时(键盘出现并立即消失,防止用户输入任何内容)。

很明显,有些东西触发了不必要的重建,但我不知道是什么以及在哪里。

当我将此页面作为主页插入时,一切正常。 当我在初始屏幕上显示动画后将页面插入其预期位置时会发生此问题,所以我想这与我的问题有关。

主类:

import 'package:flutter/material.dart';
import './view/SplashScreen.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SplashScreen(),
    );
  

启动画面:

import 'package:flutter/material.dart';
import 'dart:async';
import './UserLoader.dart';

class SplashScreen extends StatefulWidget 
  @override
  _SplashScreenState createState() => new _SplashScreenState();


class _SplashScreenState extends State<SplashScreen>
    with SingleTickerProviderStateMixin 
  AnimationController _iconAnimationController;
  CurvedAnimation _iconAnimation;

  @override
  void initState() 
    super.initState();

    _iconAnimationController = new AnimationController(
        vsync: this, duration: new Duration(milliseconds: 2000));

    _iconAnimation = new CurvedAnimation(
        parent: _iconAnimationController, curve: Curves.easeIn);
    _iconAnimation.addListener(() => this.setState(() ));

    _iconAnimationController.forward();

    startTimeout();
  

  @override
  Widget build(BuildContext context) 
    return new Material(
      color: Colors.white,
      child: new InkWell(
        child: new Center(
          child: new Container(
            width: 275.0,
            height: 275.0,
            decoration: new BoxDecoration(
              image: new DecorationImage(
                  colorFilter: new ColorFilter.mode(
                      Colors.white.withOpacity(_iconAnimation.value),
                      BlendMode.dstATop),
                  image: new AssetImage("images/logo.png")),
            ),
          ),
        ),
      ),
    );
  

  void handleTimeout() 
    Navigator.of(context).pushReplacement(new MaterialPageRoute(
        builder: (BuildContext context) => new UserLoader()));
  

  startTimeout() async 
    var duration = const Duration(seconds: 3);
    return new Timer(duration, handleTimeout);
  

错误页面:

import 'package:flutter/material.dart';

class UserLoader extends StatefulWidget 
  @override
  _UserLoaderState createState() => new _UserLoaderState();


class _UserLoaderState extends State<UserLoader> 
  @override
  Widget build(BuildContext context) 
    final _formKey = new GlobalKey<FormState>();
    final _emailController = new TextEditingController();

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Informations"),
          actions: <Widget>[
            new IconButton(
                icon: const Icon(Icons.save),
                onPressed: () 
                  // unrelated stuff happens here
                )
          ],
        ),
        body: new Center(
          child: new SingleChildScrollView(
              child: new Form(
                  key: _formKey,
                  child: new Column(children: <Widget>[
                    new ListTile(
                      leading: const Icon(Icons.email),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Email",
                        ),
                        keyboardType: TextInputType.emailAddress,
                        controller: _emailController,
                        validator: _validateEmail,
                      ),
                    ),
                  ]))),
        ));
    

谁能帮我找出为什么页面会不断地自我重建?

【问题讨论】:

“就像屏幕方向改变或文本字段获得焦点时”,我想说这是意料之中的。当屏幕的大小或格式发生变化时,需要根据新的约束重新构建 UI。 @GünterZöchbauer 嗯,我可以理解。但是我应该怎么做才能让用户在文本字段中书写,而不是每次尝试书写时都让键盘消失?如果您认为当前的写作具有误导性,请随时以更好的方式编辑问题。 如果你写的时候键盘消失了,这是由其他原因引起的。在我的评论(和你的引用)中,只提到了“位置变化”和“当文本字段获得焦点时”。 我猜这个问题是由 TextFormField 被放置在可滚动区域内引起的。我认为这是已知问题。我不知道解决方法。在 GitHub 中搜索 Flutter 问题以查找类似问题。 听起来像github.com/flutter/flutter/issues/10826 【参考方案1】:

我通过像这样简单地更改类来解决问题:

import 'package:flutter/material.dart';

class UserLoader extends StatefulWidget 
  @override
  _UserLoaderState createState() => new _UserLoaderState();


class _UserLoaderState extends State<UserLoader> 
  Widget _form; // Save the form

  @override
  Widget build(BuildContext context) 
    if (_form == null)  // Create the form if it does not exist
      _form = _createForm(context); // Build the form
    
    return _form; // Show the form in the application
  

  Widget _createForm(BuildContext context) 
    // This is the exact content of the build method in the question

    final _formKey = new GlobalKey<FormState>();
    final _emailController = new TextEditingController();

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Informations"),
          actions: <Widget>[
            new IconButton(
                icon: const Icon(Icons.save),
                onPressed: () 
                  // unrelated stuff happens here
                )
          ],
        ),
        body: new Center(
          child: new SingleChildScrollView(
              child: new Form(
                  key: _formKey,
                  child: new Column(children: <Widget>[
                    new ListTile(
                      leading: const Icon(Icons.email),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Email",
                        ),
                        keyboardType: TextInputType.emailAddress,
                        controller: _emailController,
                        validator: _validateEmail,
                      ),
                    ),
                  ]))),
        ));
    
  

希望有一天这可能对其他人有所帮助。

【讨论】:

您可能应该记下您对课程所做的更改,这样其他人就不必通过代码查找答案。 @NatoBoram 你说得对,我在代码中添加了一些 cmets 以突出显示更改:) @Sibin 对不起,这个项目对我来说有点老了,但我记得,如果电子邮件正确,我的_validateEmail 返回null,或者消息错误(一个简单的字符串)自动显示在字段下方。 Save 按钮有一个调用类似_formKey.currentState.validate() 的方法来检查是否一切正常 - 如果电子邮件错误,验证器返回 false 并阻止“保存”操作。 Flutter 很好地处理了这一切。希望这会有所帮助,否则您可以在 SO 上发布问题以获得更好的答案。 @Daneel 感谢您的回复。在我的项目中实现了streambuilder,那么现在一切都运行良好。 亲爱的,你救了我的命,也救了我的时间。请接受我最衷心的感谢【参考方案2】:

你需要做的就是移动这一行

final _formKey = new GlobalKey<FormState>();

build 方法到状态类声明(即在build 之外)。键必须在创建类时创建一次。在您的情况下,每个构建操作都会重新创建密钥。

【讨论】:

以上是关于在 Flutter 中推送新页面时,Navigator 堆栈上的页面会重建的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:如何在抽屉内推送/弹出导航器页面?

在底部导航栏上推送新屏幕时出现颤振过渡动画问题

Flutter 导航,重新打开页面而不是再次推送

flutter_bloc 库中的存储库提供程序在推送新路由时不提供存储库

每次推送新路线时,都会再次显示最后的 Flutter SnackBar

Flutter 在启动时阻止 iOS 上的推送通知权限