有颤动的筑巢路线

Ilj*_*lja 23 routing dart flutter

我试图找出以下问题的良好架构解决方案:我有以下第一级路由,也可以称为布局:

/onboarding/* -> Shows onboarding layout
/dashboard/* -> Shows dashboard layout
/overlay/* -> shows slide up overlay layout
/modal/* -> shows modal layout
Run Code Online (Sandbox Code Playgroud)

根据他/她的身份验证状态,操作等,用户被路由到这些中的每一个.我正确地得到了这个阶段.

出现问题时,我想用二级水平,可以被称为路由页面,例如

/onboarding/signin -> Shows onboarding layout, that displays signin route
/onboarding/plan -> Shows onboarding layout, that displays plan options
/modal/plan-info -> Shows modal layout, over previous page (/onboarding/plan) and displays plan-information page.
Run Code Online (Sandbox Code Playgroud)

如何以可以有效地路由到它们显示的布局和页面的方式来最好地定义/组织这些?请注意,每当我在一个布局中布置页面时,布局不会改变,但我想根据路径为内部(页面)内部更改内容(页面)设置动画.

到目前为止,我取得了以

import "package:flutter/widgets.dart";
import "package:skimitar/layouts/Onboarding.dart";
import "package:skimitar/layouts/Dashboard.dart";

Route generate(RouteSettings settings) {
  Route page;
  switch (settings.name) {
    case "/onboarding":
      page = new PageRouteBuilder(pageBuilder: (BuildContext context,
          Animation<double> animation, Animation<double> secondaryAnimation) {
        return new Onboarding();
      });
      break;
      case "/dashboard":
      page = new PageRouteBuilder(pageBuilder: (BuildContext context,
          Animation<double> animation, Animation<double> secondaryAnimation) {
        return new Dashboard();
      });
      break;
  }
  return page;
}

/* Main */
void main() {
  runApp(new WidgetsApp(
      onGenerateRoute: generate, color: const Color(0xFFFFFFFFF)));
}
Run Code Online (Sandbox Code Playgroud)

这将路由到登机和仪表板布局(现在只是简单的容器包装文本).我也相信我可以使用PageRouteBuilder后者来设置路线之间的过渡动​​画?现在我需要弄清楚如何在登机和仪表板上安装嵌套的辅助路由器.

下面是我想要实现的一些视觉表示,我需要能够成功地路由蓝色和红色位.在只要我们在这个例子中/dashboard的蓝色位(布局)并没有改变,但是当我们从说导航/dashboard/home/dashboard/stats红色位(页)应该淡出,并与新的内容褪色英寸 如果我们离开/dashboard/home/onboarding/home,红色位(布局)应该逐渐消失,连同其当前活动的页面并显示新的入门布局,故事仍在继续.

在此输入图像描述

编辑我用下面概述的方法取得了一些进展,基本上我将确定我的布局runApp,并将WidgetsApp在每个布局中声明新的和路由.它似乎工作,但有一个问题,当我点击"SignUp"我被重定向到正确的页面,但我也可以看到它下面的旧页面.

main.dart

import "package:flutter/widgets.dart";
import "package:myProject/containers/layouts/Onboarding.dart";

/* Main */
void main() {
  runApp(new Onboarding());
}
Run Code Online (Sandbox Code Playgroud)

Onboarding.dart

import "package:flutter/widgets.dart";
import "package:myProject/containers/pages/SignIn.dart";
import "package:myProject/containers/pages/SignUp.dart";
import "package:myProject/services/helpers.dart";

/* Onboarding router */
Route onboardingRouter(RouteSettings settings) {
  Route page;
  switch (settings.name) {
    case "/":
      page = buildOnboardingRoute(new SignIn());
      break;
    case "/sign-up":
      page = buildOnboardingRoute(new SignUp());
      break;
    default:
      page = buildOnboardingRoute(new SignIn());
  }
  return page;
}

class Onboarding extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      decoration: new BoxDecoration(
          color: const Color(0xFF000000),
          image: new DecorationImage(
              image: new AssetImage("assets/images/background-fire.jpg"),
              fit: BoxFit.cover)),
      child: new WidgetsApp(
          onGenerateRoute: onboardingRouter, color: const Color(0xFF000000)),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

SignUp.dart

import "package:flutter/widgets.dart";

class SignUp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Center(
        child: new Text("Sign Up",
            style: new TextStyle(color: const Color(0xFFFFFFFF))));
  }
}
Run Code Online (Sandbox Code Playgroud)

helpers.dart

import "package:flutter/widgets.dart";

Route buildOnboardingRoute(Widget page) {
  return new PageRouteBuilder(
      opaque: true,
      pageBuilder: (BuildContext context, _, __) {
        return page;
      });
}
Run Code Online (Sandbox Code Playgroud)

Yur*_*nov 12

您可以将标准Navigator用作嵌套,而无需任何其他技巧。

在此处输入图片说明

所有你需要的,就是分配一个全局键并指定必要的参数。当然,您需要关心 android 后退按钮的行为

您唯一需要知道的是,此导航器的上下文不是全局的。它将导致使用它的一些特定要点。

下面的示例稍微复杂一些,但它允许您查看如何从外部和内部为导航器小部件设置嵌套路由。在示例中,我们调用setState根页面以通过initRouteof设置新路由NestedNavigator

  import 'package:flutter/material.dart';

  void main() => runApp(App());

  class App extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Nested Routing Demo',
        home: HomePage(),
      );
    }
  }

  class HomePage extends StatefulWidget {
    @override
    _HomeState createState() => _HomeState();
  }

  class _HomeState extends State<HomePage> {
    final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Root App Bar'),
        ),
        body: Column(
          children: <Widget>[
            Container(
              height: 72,
              color: Colors.cyanAccent,
              padding: EdgeInsets.all(18),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Text('Change Inner Route: '),
                  RaisedButton(
                    onPressed: () {
                      while (navigationKey.currentState.canPop())
                        navigationKey.currentState.pop();
                    },
                    child: Text('to Root'),
                  ),
                ],
              ),
            ),
            Expanded(
              child: NestedNavigator(
                navigationKey: navigationKey,
                initialRoute: '/',
                routes: {
                  // default rout as '/' is necessary!
                  '/': (context) => PageOne(),
                  '/two': (context) => PageTwo(),
                  '/three': (context) => PageThree(),
                },
              ),
            ),
          ],
        ),
      );
    }
  }

  class NestedNavigator extends StatelessWidget {
    final GlobalKey<NavigatorState> navigationKey;
    final String initialRoute;
    final Map<String, WidgetBuilder> routes;

    NestedNavigator({
      @required this.navigationKey,
      @required this.initialRoute,
      @required this.routes,
    });

    @override
    Widget build(BuildContext context) {
      return WillPopScope(
        child: Navigator(
          key: navigationKey,
          initialRoute: initialRoute,
          onGenerateRoute: (RouteSettings routeSettings) {
            WidgetBuilder builder = routes[routeSettings.name];
            if (routeSettings.isInitialRoute) {
              return PageRouteBuilder(
                pageBuilder: (context, __, ___) => builder(context),
                settings: routeSettings,
              );
            } else {
              return MaterialPageRoute(
                builder: builder,
                settings: routeSettings,
              );
            }
          },
        ),
        onWillPop: () {
          if(navigationKey.currentState.canPop()) {
            navigationKey.currentState.pop();
            return Future<bool>.value(false);
          }
          return Future<bool>.value(true);
        },
      );
    }
  }

  class PageOne extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Page One'),
              RaisedButton(
                onPressed: () {
                  Navigator.of(context).pushNamed('/two');
                },
                child: Text('to Page Two'),
              ),
            ],
          ),
        ),
      );
    }
  }

  class PageTwo extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Page Two'),
              RaisedButton(
                onPressed: () {
                  Navigator.of(context).pushNamed('/three');
                },
                child: Text('go to next'),
              ),
              RaisedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('go to back'),
              ),
            ],
          ),
        ),
      );
    }
  }

  class PageThree extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Page Three'),
              RaisedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('go to back'),
              ),
            ],
          ),
        ),
      );
    }
  }
Run Code Online (Sandbox Code Playgroud)

您可以在下一篇文章中找到一些其他信息。

不幸的是,当您仅更改子项时,您无法在没有导航堆栈的情况下导航到相同的根小部件。因此,为了避免根小部件导航(根小部件重复),您需要创建自定义导航方法,例如基于 InheritedWidget。您将在其中检查新的根路由,如果它没有更改为仅调用子(嵌套)导航器。

因此,您需要将路由分为两部分:根导航器的“/onboarding”和嵌套导航器的“/plan”,并分别处理这些数据。

  • 很好的解决方案!然而我注意到,在热重载时,嵌套导航器的状态会丢失,我将从初始路线开始,这使得开发非常烦人。有人遇到过同样的问题吗? (2认同)

Rém*_*let 11

你不能嵌套'router'(onGenerateRoute).它是设计不可嵌套的.这是因为WidgetApp > Dashboard > Profile其他一些东西都基于它.

另一方面,您可以使用onGenerateRoute构建嵌套的"路由",在路由'/ dashboard/profile'的情况下,构建一个树NestedRoute.我认为这是你想要实现的目标.

结合更高阶的功能,您可以拥有builder为您创造的东西.

为了提供代码流的线索:builder: (child) => new Dashboard(child: child),忽略布局的确切构建,让它在buildRoute方法中(例如 PageRouteBuilder).在调用_build方法时,我们将Widgets为该页面的实例生成一个,但是让_build管理创建该页面builder.在_build我们要么使用BuildNestedRoutes原样 - 或者让它给子路径充气,通过调用请求的子路由,调用它自己的子路由NestedRoutes.完成后,我们将使用构建的子路由作为构建器的参数.长话短说,你递归地进入更远的路径级别来构建路径的最后一级,然后让它从递归中升起并使用结果作为外层的参数,依此类推.

RouteSettings为你做脏工作并解析NestedRoute构建必要的列表buildNestedRoutes.

所以,从下面的例子

示例:

@override
Widget build(BuildContext context) {
  return new MaterialApp(
    initialRoute: '/foo/bar',
    home: const FooBar(),
    onGenerateRoute: buildNestedRoutes(
      [
        new NestedRoute(
          name: 'foo',
          builder: (child) => new Center(child: child),
          subRoutes: [
            new NestedRoute(
              name: 'bar',
              builder: (_) => const Text('bar'),
            ),
            new NestedRoute(
              name: 'baz',
              builder: (_) => const Text('baz'),
            )
          ],
        ),
      ],
    ),
  );
}
Run Code Online (Sandbox Code Playgroud)

在这里,您只需定义嵌套路由(名称+关联组件).而Fooclass + Bar方法是这样定义的:

typedef Widget NestedRouteBuilder(Widget child);

@immutable
class NestedRoute {
  final String name;
  final List<NestedRoute> subRoutes;
  final NestedRouteBuilder builder;

  const NestedRoute({@required this.name, this.subRoutes, @required this.builder});

  Route buildRoute(List<String> paths, int index) {
    return new PageRouteBuilder<dynamic>(
      pageBuilder: (_, __, ___) => _build(paths, index),
    );
  }

  Widget _build(List<String> paths, int index) {
    if (index > paths.length) {
      return builder(null);
    }
    final route = subRoutes?.firstWhere((route) => route.name == paths[index], orElse: () => null);
    return builder(route?._build(paths, index + 1));
  }
}

RouteFactory buildNestedRoutes(List<NestedRoute> routes) {
  return (RouteSettings settings) {
    final paths = settings.name.split('/');
    if (paths.length <= 1) {
      return null;
    }
    final rootRoute = routes.firstWhere((route) => route.name == paths[1]);
    return rootRoute.buildRoute(paths, 2);
  };
}
Run Code Online (Sandbox Code Playgroud)

这样,您onGenerateRouteWidgetApp > Dashboard > Profile组件就不会与您的路由系统紧密耦合; 但仍然有嵌套路线.然后在整个地方派遣您的路线更具可读性.你可以轻松添加一个新的.

  • 哇,这很好。但是颤振文档没有说清楚我不知道可以做到这一点,谢谢您的时间 (3认同)