6.2.初識Flutter應用之路由管理

路由管理

路由(Route)在移動開發中一般指頁面(Page),這跟web開發中單頁應用的Route概念意義是相同的,Route在Android中一般指一個Activity,在iOS中指一個ViewController。所謂路由管理,就是管理頁面之間如何跳轉,一般也可被稱爲導航管理。這和原生開發相似,不管是Android仍是iOS,導航管理都會維護一個路由棧,路由入棧(push)操做對應打開一個新頁面,路由出棧(pop)操做對應頁面關閉操做,而路由管理主要是指如何來管理路由棧。git

咱們在上一節「計數器」示例的基礎上,作以下修改:github

1.建立一個新路由,命名「SecondPageRoute」

class SecondPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第二個路由頁面"),
      ),
    );
  }
}

新路由繼承自StatelessWidget,界面很簡單,在頁面中間顯示一句"這是第二個路由頁面"。web

2.在_MyHomePageState.build方法中的Column的子widget中添加一個按鈕(FlatButton)

Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
      ... //省略無關代碼
            FlatButton(
              child: Text("點我打開一個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.push(context,
                  new MaterialPageRoute(builder: (context) {
                    return SecondPageRoute();
                  })
                );
              },
            ),
       ],
 )

咱們添加了一個打開新路由的按鈕,並將按鈕文字顏色設置爲藍色,點擊該按鈕後就會打開新的路由頁面。ajax

avatar
avatar

完整代碼

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: '【全棧編程】- onajax.com',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: '【全棧編程】- onajax.com'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              '【全棧編程】- onajax.com提示:你已經點擊了--',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            FlatButton(
              child: Text("點我打開一個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.push(context,
                  new MaterialPageRoute(builder: (context) {
                    return SecondPageRoute();
                  })
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: '【全棧編程】- onajax.com',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class SecondPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第二個路由頁面"),
      ),
    );
  }
}

MaterialPageRoute

MaterialPageRoute繼承自PageRoute類,PageRoute類是一個抽象類,表示佔有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。編程

MaterialPageRoute 是Material組件庫的一個Widget,它能夠針對不一樣平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:bash

對於Android,當打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當關閉頁面時,當前頁面會從屏幕頂部滑動到屏幕底部後消失,同時上一個頁面會顯示到屏幕上。app

對於iOS,當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面所有顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。less

下面咱們介紹一下MaterialPageRoute 構造函數的各個參數的意義:ide

MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })

builder 是一個WidgetBuilder類型的回調函數,它的做用是構建路由頁面的具體內容,返回值是一個widget。咱們一般要實現此回調,返回新路由的實例。函數

settings 包含路由的配置信息,如路由名稱、是否初始路由(首頁)。

maintainState:默認狀況下,當入棧一個新路由時,原來的路由仍然會被保存在內存中,若是想在路由沒用的時候釋放其所佔用的全部資源,能夠設置maintainState爲false。

fullscreenDialog表示新的路由頁面是不是一個全屏的模態對話框,在iOS中,若是fullscreenDialog爲true,新頁面將會從屏幕底部滑入(而不是水平方向)。

若是想自定義路由切換動畫,能夠本身繼承PageRoute來實現,咱們將在後面介紹動畫時,實現一個自定義的路由Widget。

Navigator是一個路由管理的widget,它經過一個棧來管理一個路由widget集合。一般當前屏幕顯示的頁面就是棧頂的路由。Navigator提供了一系列方法來管理路由棧,在此咱們只介紹其最經常使用的兩個方法:

Future push(BuildContext context, Route route)

將給定的路由入棧(即打開新的頁面),返回值是一個Future對象,用以接收新路由出棧(即關閉)時的返回數據。

bool pop(BuildContext context, [ result ])

將棧頂路由出棧,result爲頁面關閉時返回給上一個頁面的數據。

Navigator 還有不少其它方法,如Navigator.replace、Navigator.popUntil等,詳情請參考API文檔或SDK源碼註釋,在此再也不贅述。下面咱們還須要介紹一下路由相關的另外一個概念「命名路由」。

實例方法

Navigator類中第一個參數爲context的靜態方法都對應一個Navigator的實例方法, 好比Navigator.push(BuildContext context, Route route)等價於Navigator.of(context).push(Route route) ,下面命名路由相關的方法也是同樣的。

命名路由

所謂命名路由(Named Route)即給路由起一個名字,而後能夠經過路由名字直接打開新的路由。這爲路由管理帶來了一種直觀、簡單的方式。

路由表

要想使用命名路由,咱們必須先提供並註冊一個路由表(routing table),這樣應用程序才知道哪一個名稱與哪一個路由Widget對應。路由表的定義以下:

Map<String, WidgetBuilder> routes;

它是一個Map, key 爲路由的名稱,是個字符串;value是個builder回調函數,用於生成相應的路由Widget。咱們在經過路由名稱入棧新路由時,應用會根據路由名稱在路由表中找到對應的WidgetBuilder回調函數,而後調用該回調函數生成路由widget並返回。

註冊路由表

咱們須要先註冊路由表後,咱們的Flutter應用才能正確處理命名路由的跳轉。註冊方式很簡單,咱們回到以前「計數器」的示例,而後在MyApp類的build方法中找到MaterialApp,添加routes屬性,代碼以下:

return new MaterialApp(
  title: '【全棧編程】- onajax.com',
  theme: new ThemeData(
    primarySwatch: Colors.blue,
  ),

  routes: {
    "third_page":(context)=>ThirdPageRoute(),
  },

  home: new MyHomePage(title: '【全棧編程】- onajax.com'),
);

//同時添加第三個頁面的代碼
class ThirdPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第三個路由頁面"),
      ),
    );
  }
}

如今咱們就完成了路由表的註冊。

經過路由名打開新路由頁

要經過路由名稱來打開新路由,可使用:

Future pushNamed(BuildContext context, String routeName,{Object arguments})
Navigator 除了pushNamed方法,還有pushReplacementNamed等其餘管理命名路由的方法,讀者能夠自行查看API文檔。

接下來咱們經過路由名來打開新的路由頁,再添加一個FlatButton的onPressed回調代碼,改成:

FlatButton(
  child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
  textColor: Colors.green,
  onPressed: () {
    //導航到一個新的路由頁面
    Navigator.pushNamed(context, "third_page");
  },
)

熱重載應用,再次點擊「點我打開第三個新頁面」按鈕,依然能夠打開新的路由頁。

avatar
avatar
avatar
avatar
avatar

完整代碼

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: '【全棧編程】- onajax.com',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),

      routes: {
        "third_page":(context)=>ThirdPageRoute(),
      },

      home: new MyHomePage(title: '【全棧編程】- onajax.com'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              '【全棧編程】- onajax.com提示:你已經點擊了--',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            FlatButton(
              child: Text("點我打開第二個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.push(context,
                  new MaterialPageRoute(builder: (context) {
                    return SecondPageRoute();
                  })
                );

              },
            ),
            FlatButton(
              child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.pushNamed(context, "third_page");
              },
            )
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: '【全棧編程】- onajax.com',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class SecondPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第二個路由頁面"),
      ),
    );
  }
}

class ThirdPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第三個路由頁面"),
      ),
    );
  }
}

命名路由參數

在Flutter最初的版本中,命名路由是不能傳遞參數的,後來才支持了參數;下面展現命名路由如何傳遞並獲取路由參數:

咱們先註冊一個路由:

routes:{
   "parameters_page":(context)=>ParametersRoute(),
  } ,

在路由頁經過RouteSetting對象獲取路由參數:

class ParametersRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    //獲取路由參數  
    var args = ModalRoute.of(context).settings.arguments
    //...省略無關代碼
  }
}

在打開路由時傳遞參數

Navigator.of(context).pushNamed("parameters_page", arguments: "【全棧編程】- onajax.com");

avatar
avatar

完整代碼

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: '【全棧編程】- onajax.com',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),

      routes: {
        "third_page":(context)=>ThirdPageRoute(),
        "parameters_page":(context)=>ParametersRoute(),
      },

      home: new MyHomePage(title: '【全棧編程】- onajax.com'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              '【全棧編程】- onajax.com提示:你已經點擊了--',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            FlatButton(
              child: Text("點我打開第二個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.push(context,
                  new MaterialPageRoute(builder: (context) {
                    return SecondPageRoute();
                  })
                );

              },
            ),
            FlatButton(
              child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
                Navigator.pushNamed(context, "third_page");
              },
            ),
            FlatButton(
              child: Text("攜帶參數打開新頁面",style: TextStyle(fontWeight: FontWeight.bold),),
              textColor: Colors.green,
              onPressed: () {
                //導航到一個新的路由頁面
//                Navigator.pushNamed(context, "third_page");
                Navigator.of(context).pushNamed("parameters_page",arguments:"【全棧編程】- onajax.com");
              },
            )
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: '【全棧編程】- onajax.com',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class SecondPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第二個路由頁面"),
      ),
    );
  }
}

class ThirdPageRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("這是第三個路由頁面"),
      ),
    );
  }
}

class ParametersRoute extends StatelessWidget {

  final Topic = Text("【全棧編程】- onajax.com");
  @override
  Widget build(BuildContext context) {
    // TODO: implement build

    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      appBar: AppBar(
        title: Text("【全棧編程】- onajax.com"),
      ),
      body: Center(
        child: Text("路由到此頁面獲取到的數據爲:\n" + args),
      ),
    );
  }
}

參照

相關文章
相關標籤/搜索