Flutter中如何進行介面跳轉

天涯一角發表於2021-03-28

在android開發中介面都是對應著一個個Activity,我們通過intent從一個介面啟動另外一個。路由(Route)在移動開發中通常指頁面(Page),所謂路由管理,就是管理頁面之間如何跳轉,通常也可被稱為導航管理。導航管理都會維護一個路由棧,路由入棧(push)操作對應開啟一個新頁面,路由出棧(pop)操作對應頁面關閉操作,而路由管理主要是指如何來管理路由棧。

一、小試牛刀

先寫一個New Route,然後主介面上包含一個按鈕,點選按鈕則跳轉到新的頁面。新頁面非常簡單,一個AppBar + 一個Text。

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("I'am new route"),
      ),
    );
  }
}
複製程式碼

接下來再看主介面包含跳轉按鈕(RaisedButton)的程式碼

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click button to open new route:',
            ),
            RaisedButton.icon(
              icon: Icon(Icons.send),
              label: Text("Go"),
              onPressed: () {
                //導航到新路由
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return NewRoute();
                }));
              },
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

下面是執行後的效果動圖:
在這裡插入圖片描述

我們看到從主介面跳轉到新頁面的程式碼非常簡單,使用了以下程式碼段,接下來學習一下這段程式碼的含義。

//導航到新路由
Navigator.push(context, MaterialPageRoute(builder: (context) {
    return NewRoute();
}));
複製程式碼

二、路由管理

儘管導航到新路由的程式碼非常簡短,但很多新的“元素”需要學習一番,不然還是不明白這段程式碼為何可以生效。

2.1 Navigator

Navigator 是一個路由管理的元件,它提供了開啟和退出路由頁方法。Navigator 通過一個棧來管理活動路由集合。通常當前螢幕顯示的頁面就是棧頂的路由。Navigator 提供了一系列方法來管理路由棧。
在這裡插入圖片描述
Future push(BuildContext context, Route route)

將給定的路由入棧(即開啟新的頁面),返回值是一個Future物件,用以接收新路由出棧(即關閉)時的返回資料。

Future pushNamed(
BuildContext context,
String routeName, {
Object arguments,
})

將命名路由入棧,arguments為攜帶引數,返回值是一個Future物件,用以接收新路由出棧時的返回資料。

bool pop(BuildContext context, [ T result ])

將棧頂路由出棧,result為頁面關閉時返回給上一個頁面的資料。

Navigator 還有很多其它方法就不再介紹。

2.2 MaterialPageRoute

MaterialPageRoute繼承自PageRoute類,PageRoute類是一個抽象類,表示佔有整個螢幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關介面及屬性。MaterialPageRoute 是Material元件庫提供的元件,它可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:

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

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

下面我們介紹一下 MaterialPageRoute 建構函式的各個引數的意義:

MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })
複製程式碼

builder 是一個WidgetBuilder型別的回撥函式,它的作用是構建路由頁面的具體內容,返回值是一個widget。我們通常要實現此回撥,返回新路由的例項。

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

maintainState 預設情況下,當入棧一個新路由時,原來的路由仍然會被儲存在記憶體中,如果想在路由沒用的時候釋放其所佔用的所有資源,可以設定maintainState為false。

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

三、命名路由

所謂“命名路由”(Named Route),就是可以先給路由起一個名字,然後就可以通過路由名字直接開啟新的路由了,這為路由管理帶來了一種直觀、簡單的方式。

3.1 路由表

要想使用命名路由,我們必須先提供並註冊一個路由表(routing table),這樣應用程式才知道哪個名字與哪個路由元件相對應。路由表的定義如下:

Map<String, WidgetBuilder> routes;

它是一個Map,key為路由的名字,是個字串;value是個builder回撥函式,用於生成相應的路由widget。我們在通過路由名字開啟新路由時,應用會根據路由名字在路由表中查詢到對應的WidgetBuilder回撥函式,然後呼叫該回撥函式生成路由widget並返回。

3.2 路由表註冊

在MyApp類的build方法中找到MaterialApp,新增routes屬性,"new_page"就對應NewRoute路由。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //註冊路由表
      routes:{
        "new_page":(context) => NewRoute(),
      } ,
      home: MyHomePage(),
    );
  }
}
複製程式碼

3.3 由路由名開啟新路由

使用pushNamed方法實現,程式碼如下

Navigator.pushNamed(context, "new_page");
複製程式碼

我們會發現執行效果和上面的Demo一樣。

四、路由使用

接下來我們通過幾個例子來考察路由的使用。

4.1 路由傳值

修改小試牛刀例子中的NewRoute程式碼,新增一個country String Field,並在Text上顯示出對應的內容。

class NewRoute extends StatelessWidget {
  NewRoute({@required this.country}) : super();

  final String country;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("I'am new route country=$country"),
      ),
    );
  }
}
複製程式碼

修改MyHomePage中的程式碼,如下:

Navigator.push(context, MaterialPageRoute(builder: (context) {
    return NewRoute(country:"中國");
}));
複製程式碼

來看一下執行結果,已經將“中國”顯示在Text上了。
在這裡插入圖片描述

再來看使用pushNamed傳值的情況,和上面的效果一樣。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //註冊路由表
      routes: {
        "new_page": (context) => NewRoute(),
      },
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click button to open new route:',
            ),
            RaisedButton.icon(
              icon: Icon(Icons.send),
              label: Text("Go"),
              onPressed: () {
                //導航到新路由
                Navigator.pushNamed(context, "new_page", arguments: "中國");
              },
            ),
          ],
        ),
      ),
    );
  }
}

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("I'am new route country=$args"),
      ),
    );
  }
}
複製程式碼

4.2 路由返回值

NewRoute路由增加了一個返回按鈕,通過Navigator.pop方法返回字串“我是返回值”。MyHomePage Navigator.pushNamed方法返回一個Future物件,我們從中接收返回值,看日誌列印即可。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click button to open new route:',
            ),
            RaisedButton.icon(
              icon: Icon(Icons.send),
              label: Text("Go"),
              onPressed: () {
                //導航到新路由
                Navigator.pushNamed(context, "new_page", arguments: "中國")
                    .then((data) {
                  //接受返回的引數
                  print(data.toString());
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
        appBar: AppBar(
          title: Text("New route"),
        ),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              Text("I'am new route country=$args"),
              RaisedButton.icon(
                icon: Icon(Icons.arrow_back),
                label: Text("Back"),
                onPressed: () => Navigator.pop(context, "我是返回值"),
              ),
            ])));
  }
}
複製程式碼

日誌:

I/flutter ( 3996): 我是返回值

執行動畫:
在這裡插入圖片描述

更進一步將返回值顯示的主介面上,MyHomePage1改為StatefulWidget。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //註冊路由表
      routes: {
        "new_page": (context) => NewRoute(),
      },
      home: MyHomePage1(),
    );
  }
}

class MyHomePage1 extends StatefulWidget {
  @override
  _MyHomePageState1 createState() => _MyHomePageState1();
}

class _MyHomePageState1 extends State<MyHomePage1> {
  String _back = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click button to open new route:$_back',
            ),
            RaisedButton.icon(
              icon: Icon(Icons.send),
              label: Text("Go"),
              onPressed: () {
                //導航到新路由
                Navigator.pushNamed(context, "new_page", arguments: "中國")
                    .then((data) {
                  //接受返回的引數
                  _back = data.toString();
                  print(_back);
                  setState(() {});
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

執行結果如下:
在這裡插入圖片描述

相關文章