天天看點

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學習筆記:路由和包管理