天天看点

Flutter学习笔记:路由和包管理

4. 路由导航和包管理

代码地址

这次学习笔记主要有一下几个部分

  • 路由导航
    • 简单路由
    • 路由表导航
    • 路由传参
    • 路由参数回流
  • 包管理
    • 本地组件引用
    • 外部包引用
    • 构建本地包并引用

1. 路由导航

导航实现页面的切换,在Flutter中实现路由切换包括非具名路由和具名路由两种,如果是非具名路由需要指定具体到的路由组件(通常是一个页面),具名路由通常使用路由表构建的,建立了路由和页面组件之间一一对应的关系,接下来是具体实现。

1. 简单路由

简单路由主要通过Navigator.push来实现

static Future**<T>** push<T extends Object**>(BuildContext context, Route<T>** route)

  • 定义一个新页面的组件
  • 使用Navigator.push方法指定到特定的页面
  • 第二个参数通过MaterialPageRoute进行页面导航

定义一个路由页面组件

class Counter extends StatefulWidget {
  Counter({Key key}) : super(key: key);

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(children: <Widget>[
        Text('现在的数字是$_count'),
        FlatButton(
          onPressed: () {
            setState(() {
              ++_count;
            });
          },
          child: Text('按我++'),
          color: Colors.red,
        )
      ]),
    );
  }
}
           

其他页面通过Navigator进行路由

class NextRoute extends StatelessWidget {
  const NextRoute({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Next Route'),
      ),
      body: Center(
          child: Column(children: <Widget>[
        Text('自定义的Route包'),
        RaisedButton(
          onPressed: () {
            Navigator.push(
                context, MaterialPageRoute(builder: (context) => Counter()));
          },
          child: Text('简单路由'),
        )
      ])),
    );
  }
}
           
2. 使用路由表

路由表定义方法:

  • 定义路由页面
  • MaterialApp中的routes中以Map<String, Function>的形式定义路由表
  • 页面跳转的时候使用pushNamed进行路由链接
static Future<T> pushNamed<T extends Object>(
    BuildContext context,
    String routeName, {
    Object arguments,
   }) {
    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
  }
           

直接使用pushNamed会调用Navigator.of(context).pushNamed,因此我们也可以直接使用Navigator.of来进行路由跳转

代码实现

路由表定义

使用MaterialApp的routes字段

class HomePage extends StatelessWidget {
  const HomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    List<Message> list =
        List.generate(20, (i) => Message('我是信息$i, 读完给我回个信息', '广告商人', '重要信息$i'));
    return MaterialApp(
      title: 'Center Message',
      home: MessageCenter(messageList: list),
      routes: {
        "home": (context) => MessageCenter(messageList: list),
        "route1": (context) => NewRoute(),
        "route2": (context) => NewRoute2(),
        "route3": (context) => Route3.NextRoute(),
        "route4": (context) => Route4.NextRoute(),
        "route4/counter": (context) => Route4.Counter()
      },
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute(builder: (context) {
          print(
              'settings name = ${settings.name}, arguements=${settings.arguments}');
          return NewRoute2();
        });
      },
    );
  }
}
           

利用路由表导航

点击按钮后会自动路由到route1的页面

// 省略上下文代码
RaisedButton(
    onPressed: () {
        Navigator.pushNamed(context, 'route1');
    },
    child: Text('去路由页面1'),
),
           
3. 路由传参

非具名路由传参,主要通过函数类传参的方式,在调用时候传参,然后将对应的参数直接拿来用就好了

// 定义组件
class WithParam extends StatelessWidget {
  final String text;
  const WithParam({Key key, @required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Scaffold(
        appBar: AppBar(
          title: Text('代参路由'),
        ),
        body: Text('$text'),
      ),
    );
  }
}

           

传参调用

// 在路由调用该类的时候传参
RaisedButton(
    onPressed: () {
        Navigator.push(context, MaterialPageRoute(builder: (context) {
            return WithParam(
                text: '显示Param',
            );
        }));
    },
    child: Text('显示Param'),
)
           

路由表传参

  • 组件中通过ModalRoute.of(context).settings.arguments来获得通过路由传递的参数
  • 调用的时候通过Navigator.of(context).pushNamed(Widget(), arguments: param), 将参数加到路由上

定义组件

class NewRoute2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    dynamic args = ModalRoute.of(context).settings.arguments;
    DateTime newTime;

    Function getNewTime(DateTime time) {
      return () {
        newTime = time;
      };
    }

    print(args);
    return Scaffold(
      appBar: AppBar(
        title: Text('路由页面2'),
      ),
      body: Center(
        child: Center(
            child: Column(
          children: <Widget>[
            Text('用户名: ${args['name']}'),
            Text('生日: ${args['birth']}'),
            Text('喜好: ${args['hobby'] ?? '没有兴趣'}'),
            RaisedButton(
              onPressed: () {
                Navigator.pop(context, "这是pop的信息1");
              },
              child: Text('Pop 信息1'),
            ),
            RaisedButton(
              onPressed: () {
                Navigator.pop(context, '这是pop的信息2');
              },
              child: Text('Pop 信息2'),
            ),
            PickTime(
              onChange: getNewTime,
            )
          ],
        )),
      ),
    );
  }
}

// 将参数传递继承到路由表中
// 通过这种方法需要在NewRoute2中添加变量text
// 之后通过text来完成后面的逻辑
routes: {
    "home": (context) => MessageCenter(messageList: list),
    "route1": (context) => NewRoute(),
    "route2": (context) => NewRoute2(text: ModalRoute.of(context).settings.arguments),
    "route3": (context) => Route3.NextRoute(),
    "route4": (context) => Route4.NextRoute(),
    "route4/counter": (context) => Route4.Counter()
}
           

带参的路由跳转

通过pushNamed的arguments参数会将参数注册到Context中,之后通过context中的setting即可完成后面的操作了

RaisedButton(
    onPressed: () async {
        Navigator.of(context)
            .pushNamed('route2', arguments: userInfo)
            .then((reply) {
                return showDialog(
                    context: context,
                    builder: (BuildContext context) {
                        return AlertDialog(
                            title: Text('回传内容'),
                            content: Text('$reply'),
                        );
                    });
            });
    },
    child: Text('去路由页面2'),
)

           
4. 路由参数回流

有些业务场景比如点击打开表单或者选择选择一些东西,之后需要将选择的内容传回第一个页面中,这种场景的话,需要路由得到的参数能够回流到之前的页面的这种应用

非路由表路由传参

static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
    return Navigator.of(context).push(route);
}

Future<T> pushNamed<T extends Object>(
    String routeName, {
        Object arguments,
    }) {
    return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
           

push和pushNamed方法本身是一个Future,类似于Promise,通过类似async/await语法可以得到结果,也可以通过.then方法得到结果

  • async和await回调函数的例子
_navigateWithParam(BuildContext context, Message message) async {
    final result = await Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => MessageDetail(
                  message: message,
                )));
    print(result);
  }
           
  • 使用.then的例子
Navigator.of(context)
    .pushNamed('route2', arguments: userInfo)
    .then((reply) {
        return showDialog(
            context: context,
            builder: (BuildContext context) {
                return AlertDialog(
                    title: Text('回传内容'),
                    content: Text('$reply'),
                );
            });
    });
           

上面这段和下面这个实现效果相同

onPressed: () async {

    var reply = await Navigator.of(context)
        .pushNamed('route2', arguments: userInfo);
    showDialog(
        context: context,
        builder: (BuildContext context) {
            return AlertDialog(
                title: Text('回传内容'),
                content: Text('$reply'),
            );
        });
},
           

2. 包管理

1. 引用本地组件
Flutter学习笔记:路由和包管理

想要在main.dart中调用nextRoute.dart的文件

使用下面这两个办法都行

import './nextRoute.dart' as Route3;
import 'package:navigator/nextRoute.dart' show NextRoute;
           
  • 通过as 改变该包的名称,之后通过Route3.componentName来调用对应组件模块
  • 通过show来实现部分导入,这里通过第二条导入语句只导入NextRoute这一个组件
2. 调用外部包

dart的包管理主要通过pubspec.yaml文件,通过配置该文件之后通过 flutter package get会自动获取类似依赖,使用办法

  • 换源,为了保证速度建议换源
    export PUB_HOSTED_URL=https://pub.flutter-io.cn
    export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
               
  • 配置yaml文件
    • name为该包的名称
    • description是描述
    • environment是运行环境
    • version版本号

    上面的主要对外部发布的包比较重要,如果是项目中应用,我们只需要设置dependecies即可,输入对应依赖的版本号即可

    国内查询包的地址:flutter镜像站

使用示例

  • 找到flutter_dateTime_picker
Flutter学习笔记:路由和包管理
  • 编写dependencies
Flutter学习笔记:路由和包管理
  • 保存文件后,会自动收集依赖,依赖收集成功后通过文件即可导入
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
class PickTime extends StatefulWidget {
  final Function onChange;

  PickTime({Key key, @required this.onChange}) : super(key: key);

  @override
  PickTimeState createState() => PickTimeState(onChange: onChange);
}

class PickTimeState extends State<PickTime> {
  final Function onChange;
  DateTime dateTime;

  PickTimeState({Key key, @required this.onChange});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: FlatButton(
        child: Text('${dateTime ?? '请选择正确时间'}'),
        onPressed: () {
          DatePicker.showDatePicker(context,
              showTitleActions: true,
              minTime: DateTime(2018, 3, 5),
              maxTime: DateTime(2019, 6, 7), onChanged: (date) {
            print('change $date');
          }, onConfirm: (date) {
            print('confirm $date');
            setState(() {
              dateTime = date;
              onChange(date);
            });
          }, currentTime: DateTime.now(), locale: LocaleType.zh);
        },
      ),
    );
  }
}
           
3. 编写一个自己的包
  • 至少一个dart组件(编写的组件)
  • 有一个pubspec.yaml文件(引用这个包所用到的库)
Flutter学习笔记:路由和包管理

dart和.yaml文件是我们需要写的,其他都会自动生成

引入自己包的办法:使用path指向自己所在的相对地址

Flutter学习笔记:路由和包管理