如何在 Flutter 的小部件树中将新的 MaterialPageRoute 作为子项打开

Posted

技术标签:

【中文标题】如何在 Flutter 的小部件树中将新的 MaterialPageRoute 作为子项打开【英文标题】:How to open new MaterialPageRoute as a child in widget tree in Flutter 【发布时间】:2020-08-03 10:51:21 【问题描述】:

在下面的示例中,当我推送新的 MaterialPageRoute 时,它​​会在 Flutter 小部件树中的 Home 小部件的同一级别上创建。我希望它作为小部件 Home 的子级,因此 Home 将是 Child 小部件的父级。

这是一个完整的代码:

import 'package:flutter/material.dart';

void main() 
  runApp(MaterialApp(
    home: Home(),
  ));


class Home extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Column(
        children: <Widget>[
          Center(
            child: Text("This is home"),
          ),
          RaisedButton(
            child: Text("Open child view"),
            onPressed: () 
              Navigator.of(context)
                  .push(MaterialPageRoute(builder: (context) => Child()));
            ,
          )
        ],
      ),
    );
  


class Child extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
        body: Container(
      child: Text("Child view"),
    ));
  

这是小部件树的样子

我想实现这一点,因为我想在 Home 小部件中初始化 ScopedModel 并让它在我创建的每个新 MaterialPageRoute 中都可用。

【问题讨论】:

【参考方案1】:

我使用嵌套导航器解决了我的问题。

示例如下:

import 'package:flutter/material.dart';

void main() 
  runApp(MaterialApp(
    home: Home(),
  ));


var homeNavigatorKey = GlobalKey<NavigatorState>();

class Home extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Navigator(
        key: homeNavigatorKey,
        onGenerateRoute: (settings) 
          /*dirty code for illustration purposes only*/
          if (settings.name == '/child') 
            return MaterialPageRoute(builder: (context) => Child());
           else 
            return MaterialPageRoute(
                builder: (context) => Column(
                      children: <Widget>[
                        Center(
                          child: Text("This is home"),
                        ),
                        RaisedButton(
                          child: Text("Open child view"),
                          onPressed: () 
                            homeNavigatorKey.currentState.pushNamed('/child');
                          ,
                        )
                      ],
                    ));
          
        ,
      ),
    );
  


class Child extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
        body: Container(
      child: RaisedButton(
        onPressed: () => Navigator.of(context).pop(),
        child: Text("Back to home"),
      ),
    ));
  


这棵树是这样的

并允许将数据从 Home 传递给每个孩子。

如果您有任何更简单的解决方案,请随时发表评论或发布答案。

【讨论】:

【参考方案2】:

Navigator.of(context).push() 将用新屏幕替换您的Home 屏幕(同时将Home 屏幕保留在内存中,以便您可以返回到它)。它们在小部件树中处于同一级别,嵌套它们的唯一方法是使用嵌套的Navigators 我认为。

但是,考虑到您想要实现的目标:为什么不将 ScopedModel 向上初始化一级并在小部件树的根目录中提供呢?这样您就可以在Home 屏幕和Child 屏幕中访问您的模型。

下面是你如何做到这一点,我添加了一个简单的 ScopedModel 并且能够在Child 屏幕内访问它的textproperty。

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:tryout/model.dart';

void main() 
  runApp(
    ScopedModel<TestModel>(
      model: TestModel(),
      child: MaterialApp(
        home: Home(),
      ),
    )
  );


class Home extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Column(
        children: <Widget>[
          Center(
            child: Text("This is home"),
          ),
          RaisedButton(
            child: Text("Open child view"),
            onPressed: () 
              Navigator.of(context)
                  .push(MaterialPageRoute(builder: (context) => Child()));
            ,
          )
        ],
      ),
    );
  


class Child extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
        body: Container(
      child: Text("$ScopedModel.of<TestModel>(context).text"),
    ));
  

这是TestModel 类:

import 'package:scoped_model/scoped_model.dart';

class TestModel extends Model 
  String _text = "Test to see if this works!";
  String get text => _text;

【讨论】:

感谢您的回答。我知道这种可能性,这就是我目前实现它的方式,但是我有一个具有多个不同模型的复杂应用程序,我希望在重新创建特定小部件时重新创建其中的一些(此示例中的 Home 代表此小部件)。我想知道是否有比实现嵌套导航器更简单的方法。 啊,好的。也许您可以通过在执行 Navigators.push() 之前首先处理已经初始化的模型来做到这一点? 此时我在这些模型中有一个 dispose() 方法,但我不想手动清理模型,因为每次新程序员向模型添加新变量/功能时,他都必须记得在 dispose() 方法中处理它,我不喜欢它。您认为有可能为已经初始化的模型分配一个全新的对象实例吗?我的意思是ScopedModel.of&lt;TestModel&gt;(context) = TestModel()? 我不知道在您的小部件树中重新创建实例的可能性。通过 Child 小部件的构造函数传递一个新实例,而不是让 InheritedWidget 为您工作,我认为您不应该这样做,因为它会创建模型的多个实例和状态,这与包是关于什么的。 但是,为什么首先需要一个新实例?重建模型应该解决什么问题?

以上是关于如何在 Flutter 的小部件树中将新的 MaterialPageRoute 作为子项打开的主要内容,如果未能解决你的问题,请参考以下文章

Flutter/Dart:我的小部件树中可以有多个带有状态的小部件吗?

如何在不创建新的小部件树分支的情况下重用相同的布局屏幕

Flutter:检测任何在屏幕上不可见但在小部件树中的小部件的重建

如何在 Android Studio 中为 Flutter 上的小部件测试进入调试模式?

我们可以在 Flutter 中创建一个新的小部件吗?

如何创建自定义颤振 sdk 小部件,重建颤振和使用新的小部件