Flutter中的导航Navigation总结

在Flutter中导航到一个新页面,通常我们会使用Navigator.of(context).push()来处理,或者是调用pushNamed("\xxx"),pop()等 。

但是这里有几个问题,

  1. 需要使用到context,这就限制来导航的使用场景,必须要在一个Widget中;
  2. 第二个问题是,如果在Widget中写导航相关的代码,那么就在UI层混入了逻辑层的代码,路由导航属于业务逻辑,这不利于有一个清晰的架构;
  3. 所有的调用者都必须要清楚导航逻辑,这使得代码变得强耦合,一旦导航逻辑修改了,所有的调用处都需要修改;

所以,本文将使用get_it库和命名路由named-routing的方式来解决以上问题。

关于get_it,后面将再出一篇文章详细介绍。


命名路由named-routing

步骤:

  1. 在MaterialApp中有个onGenerateRoute属性,在这里指定一个方法generateRoute,方法接收一个RouteSettings参数,返回一个Route<dynamic>
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      onGenerateRoute: router.generateRoute,
      initialRoute: HomeViewRoute,
      home: HomeView(),
    );
  }
}
  1. 要导航的一些View
class HomeView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Home'),),
    );
  }
}

class LoginView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Login'),),
    );
  }
}
  1. 创建一个routing_constants.dart来管理所有的路由名称
const String HomeViewRoute = '/';
const String LoginViewRoute = 'login';
  1. 创建router.dart,管理所有的路由逻辑
import 'package:flutter/material.dart';
import 'package:flutter_navigation/routing_constants.dart';
import 'package:flutter_navigation/view/HomeView.dart';
import 'package:flutter_navigation/view/LoginView.dart';
import 'package:flutter_navigation/view/UndefineView.dart';

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case HomeViewRoute:
      return MaterialPageRoute(builder: (context) => HomeView());
    case LoginViewRoute:
      var loginArgument = settings.arguments;
      return MaterialPageRoute(builder: (context) => LoginView(argument: loginArgument));
    default:
      return MaterialPageRoute(
          builder: (context) => UndefinedView(name: settings.name));
  }
}
  1. 要导航的时候,直接调用:
Navigator.pushNamed(context, LoginViewRoute);
  1. 可以设置一个默认的路由页面UndefinedView,用于处理异常场景
import 'package:flutter/material.dart';

class UndefinedView extends StatelessWidget {
  final String name;

  const UndefinedView({Key key, this.name}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Route for $name is not defined'),
      ),
    );
  }
}

也可以像下面这样处理,但是不建议采用,因为下面的方式将导航的业务逻辑混入到了View层中,

return MaterialApp(
  title: 'Named Routing',
  onGenerateRoute: router.generateRoute,
  onUnknownRoute: (settings) => MaterialPageRoute(
      builder: (context) => UndefinedView(
            name: settings.name,
          )),
  initialRoute: HomeViewRoute,
);

关于跳转时,参数的传递和返回

调用:

Navigator.pushNamed(context, LoginViewRoute, arguments: 'Data Passed in');

在传递时,在路由管理类里加上传递参数的逻辑

switch (settings.name) {
  ...
 case LoginViewRoute:
      var loginArgument = settings.arguments;
      return MaterialPageRoute(builder: (context) => LoginView(argument: loginArgument));
}

同时修改LoginView的代码,来接收参数,

class LoginView extends StatelessWidget {
  final String argument;
  const LoginView({Key key, this.argument}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        Navigator.pop(context, 'fromLogin');
        return false;
      },
      child: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            Navigator.pop(context, 'fromLogin');
          },
        ),
        body: Center(
          child: Text('Login $argument'),
        ),
      ),
    );
  }
}

这里使用到了WillPopScope这个Widget,这个就是用来重载系统的返回键的,其应该用在单个页面上,如果返回了false,那么就不响应系统的返回键,当然也可以加入自己的逻辑,返回一些参数。

到这里已经实现了统一管理导航的逻辑了,接下来实现不使用context来处理导航。


跳转时,不使用context

在Flutter中,GlobalKeys可用于访问StatefulWidget的状态,利用这一点,我们可以在context以外的地方来访问NavigatorState。

  1. 添加get_it库 get_it: ^3.0.0+1
  2. 创建一个全局的Service Locator
import 'package:get_it/get_it.dart';

import 'NavigateService.dart';

final GetIt locator = GetIt.instance;

void setupLocator() {
  locator.registerSingleton(NavigateService());
}

  1. 创建一个NavigateService
import 'package:flutter/material.dart';

class NavigateService {
  final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey(debugLabel: 'navigate_key');

  NavigatorState get navigator => navigatorKey.currentState;

  get pushNamed => navigator.pushNamed;

  get push => navigator.push;
}

  1. 在runApp 之前初始化
void main() {
  setupLocator();
  runApp(App());
}
  1. MaterialAppnavigatorKey属性中 绑定NavigateService中的 GlobalKey
import 'package:flutter/material.dart';
import 'package:flutter_navigation/router.dart' as router;
import 'package:flutter_navigation/routing_constants.dart';
import 'package:flutter_navigation/service_locator.dart';
import 'package:flutter_navigation/view/HomeView.dart';

import 'NavigateService.dart';

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

class MyApp extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      onGenerateRoute: router.generateRoute,
      initialRoute: HomeViewRoute,
      navigatorKey: locator<NavigateService>().navigatorKey,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomeView(),
    );
  }
}

完整代码参考:
https://github.com/chenyucheng97/flutter_navigation

参考:
Flutter | 通过 ServiceLocator 实现无 context 导航
Flutter Navigation Cheatsheet - A Guide to Named Routing
Navigate Without BuildContext in Flutter using a Navigation Service

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值