在Flutter中,当推入一个新页面时,导航器堆栈上的页面会重新构建。

14

我正在开发一个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,
                      ),
                    ),
                  ]))),
        ));
    }}

有人能帮忙找出这个页面为什么一直在不停地重建吗?


2
当屏幕方向改变或文本字段获得焦点时,我会说这是可以预期的。当屏幕的尺寸或格式发生变化时,UI 需要根据新的限制进行重建。 - Günter Zöchbauer
@GünterZöchbauer 我能理解这一点。但是我应该怎么做才能让用户在文本字段中编写,而不是每次尝试编写时键盘都消失?如果您认为当前的写作有误导性,请随意编辑问题。 - Daneel
2
我猜问题是由于TextFormField被放置在可滚动区域内引起的。我认为这是已知的问题。我不知道有什么解决方法。在GitHub上搜索Flutter问题以查找类似的问题。 - Günter Zöchbauer
1
听起来像是 https://github.com/flutter/flutter/issues/10826 - Albert Lardizabal
我曾经遇到过类似的问题。在我的情况下,导航是在点击按钮时触发的。而不是每次实例化一个新的UserLoader(),我在类开始时将其声明为final变量。 - William Dias
显示剩余2条评论
2个回答

13

我通过简单地更改类别来解决了这个问题:

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,
                      ),
                    ),
                  ]))),
        ));
    }
  }
}

希望这能在未来某一天对别人有所帮助。


10
你应该记录你对类所做的更改,这样其他人就不必查看代码来找答案。 - Nato Boram
1
@NatoBoram 你是对的,我在代码中添加了一些注释来突出变化 :) - Daneel
1
@Sibin 抱歉,这个项目对我来说有点过时了,但是我记得我的 _validateEmail 如果电子邮件正确,则返回 null,否则返回一个错误消息(一个简单的字符串),该消息会自动显示在字段下方。保存按钮调用了类似 _formKey.currentState.validate() 的方法来检查是否一切正常 - 如果电子邮件不正确,则验证器返回 false 并阻止“保存”操作。Flutter 处理得很好。希望这可以帮助你,否则你可以在 SO 上发布问题以获得更好的答案。 - Daneel
1
@Daneel 感谢您的回复。在我的项目中实现了 StreamBuilder,现在一切都运行得非常完美。 - Sibin
1
我遇到了这个问题几天了,一直在寻找解决方案,但我的问题仍然存在。但是你的解决方案帮了我很多,非常感谢。 - ff .n
显示剩余4条评论

5
你所需做的就是移动这一行。
final _formKey = new GlobalKey<FormState>();

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接