Flutter小白教程系列(五) --- 頁面路由導航及傳參

Carlwang發表於2020-01-21

轉載請註明出處: learnandfish.com/

概述

每個應用都有很多個頁面,在flutter中同樣也有很多頁面,被稱之為路由(Router),頁面之間的跳轉通過導航器(Navigator)進行管理。 其中 Navigator.push 和 Navigator.pop 是最簡單的跳轉到新頁面和返回到上一級介面的方式。

路由分為靜態路由(即命名路由)和動態路由。頁面之間跳轉時往往需要傳遞引數,這稱之為路由傳值。下面我們會一一帶領大家學習。

通過本篇文章的學習我們的目標是熟練掌握路由及傳值,以後進行應用開發時對頁面跳轉方面不再疑惑。

靜態路由(即命名路由)

flutter中萬物皆widget,我們的頁面(route)也是widget的子類,所以我們定義一個介面也是通過繼承widget實現。 前面的部落格我們已經定義過介面了,比如計數器例項,就是一個簡單的頁面,也就是一個路由。下面我們來詳細實現一個介面。 首先我需要一個入口函式,這個相信大家已經很熟悉了,就是在main方法中呼叫runApp函式進入應用,我們就不做詳細介紹了,直接給出程式碼。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 常用固定寫法,生成Material風格的App
    return MaterialApp(
      title: "路由使用",
      theme: ThemeData(
        // 預設為亮色主題,可以設定[Brightness.dark]變成黑暗模式
        brightness: Brightness.light,
      ),
      home: HomePage(), // 首頁面
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text("主頁面"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第一個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第一個介面,點選我進入第二個介面");
          },
        ),
      ),
    );
  }
}

複製程式碼

上面的程式碼是我們最常規的包含一個主頁面的應用。後續我們寫應用時候的基本框架也是在此基礎上進行擴充套件。 現在我們的想法是點選這個頁面上的按鈕跳轉的第二個介面,首先我們需要構造第二個介面。構造第二個介面其實和我們構造第一個 介面HomePage一樣,繼承widget重寫自己想要的樣式即可。實現了頁面就要開始跳轉邏輯。

靜態路由即命名路由,在通過Navigator進行跳轉之前,需要在MaterialApp元件內顯式宣告路由的名稱,一旦宣告,路由的跳轉 方式就固定了,所以稱之為靜態路由,有唯一的名稱所以也稱之為命令路由。顯式宣告路由通過在MaterialApp內的routes屬性進行定義。

如果我們有很多個頁面和很多個其他型別的元件都放在lib下,對於後期維護簡直是一大折磨,所以分包是大多數平臺的常規操作, 就是對有同一種特性的東西放置在同一個包下,比如頁面類的元件都放在pages包,工具類的元件放在utils包下等。 接下來我們就新建一個pages包,把第二個介面SecondPage放進去,把第一個介面HomePage也提取出來放到這個包下。

我們分為一下三步進行靜態路由的跳轉:

  • 首先在lib目錄右鍵新建pages包,接著在pages包下新建SecondPage.dart檔案,然後把HomePage提取到pages下,成為單獨的類。
  • 在RouteDemo類中的MaterialApp內宣告routes屬性,為了顯示宣告路由的名稱。
  • 使用Navigator進行頁面的跳轉和返回。
import 'package:flutter/material.dart';

// 引入頁面路徑
import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 常用固定寫法,生成Material風格的App
    return MaterialApp(
      title: "路由使用",
      theme: ThemeData(
        // 預設為亮色主題,可以設定[Brightness.dark]變成黑暗模式
        brightness: Brightness.light,
      ),
      // 預設載入的頁面
      initialRoute: '/', // 首頁面
      // 顯式宣告介面列表
      routes: {
        '/': (context) => HomePage(),
        '/secondPage': (context) => SecondPage(),
      },
    );
  }
}
複製程式碼

首頁面單獨提取出來之後的程式碼如下。

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text("主頁面"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第一個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第一個介面,點選我進入第二個介面");
            // 跳轉到第二個介面
            Navigator.pushNamed(context, '/secondPage');
          },
        ),
      ),
    );
  }
}
複製程式碼

第二個頁面提取之後的程式碼。

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text("第二個介面"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第二個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第二個介面,點選我返回到第一個介面");
            // 返回上一個介面
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}
複製程式碼

對於命名路由的跳轉,通過Navigator.pushNamed方法呼叫,通過Navigator.pop方法返回上一級介面。

動態路由

動態路由不需要顯示宣告,直接通過程式碼實現。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 常用固定寫法,生成Material風格的App
    return MaterialApp(
      title: "路由使用",
      theme: ThemeData(
        // 預設為亮色主題,可以設定[Brightness.dark]變成黑暗模式
        brightness: Brightness.light,
      ),
      home: HomePage(),
    );
  }
}
複製程式碼

在HomePage介面通過呼叫Navigator.push方法實現跳轉。第二個頁面的返回邏輯不變。

Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => SecondPage(),
                ));
複製程式碼

動態路由的相互傳參

有時候我們不僅需要跳轉到對應介面,還需要傳遞一些引數給下一個介面,同時下一個介面返回時,把某些引數再次傳遞給該介面。 我們修改SecondPage元件的構造方法,為了接收需要傳遞的引數。這時候我們第二個頁面結構如下:

class SecondPage extends StatelessWidget {
  // 定義一個需要變數, 接收傳遞的引數
  final String title;

  // 為title設定一個預設引數,這樣的跳轉該介面時可以不傳值。
  SecondPage({Key key, this.title = "第二個介面"});

  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第二個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第二個介面,點選我返回到第一個介面");
            // 返回上一個介面
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}
複製程式碼

第一個介面跳轉的地方程式碼是這樣的。

Navigator.push(
                context,
                MaterialPageRoute(
                  // 傳遞title為SecondPage,跳轉到第二個介面就會把標題設定為SecondPage
                  builder: (context) => SecondPage(title: "SecondPage"),
                ));
          },
複製程式碼

說完了從第一個頁面往第二個頁面傳遞了引數,如果第二個頁面返回時傳遞一句話,然後第一個頁面接收到這句話然後列印出來, 程式碼修改如下:

// HomePage頁面程式碼
Navigator.push(
                context,
                MaterialPageRoute(
                  // 傳遞title為SecondPage,跳轉到第二個介面就會把標題設定為SecondPage
                  builder: (context) => SecondPage(title: "SecondPage"),
                  // 呼叫then等待接收返回資料
                )).then((value) => print(value));

// SecondPage頁面程式碼
 Navigator.pop(context, "返回傳遞資料");
複製程式碼

靜態路由(即命名路由)的相互傳參

講完了動態路由及動態路由傳參之後,我們來講一下靜態路由傳參,引數的傳遞方式是flutter為我們定義好的,我們只需要把固定 程式碼拷貝回來,稍微修改即可。為了更具有普遍性,我們再定義一個頁面ThirdPage。

在我們顯示宣告瞭routes之後,還需要在MaterialApp元件內新增onGenerateRoute屬性內容進行引數傳遞的處理。 完整程式碼如下:

import 'package:flutter/material.dart';

import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';
import 'pages/ThirdPage.dart';

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

class MyApp extends StatelessWidget {
  // 宣告所有的頁面
  final routes = {
    '/': (context, {arguments}) => HomePage(),
    '/secondPage': (context, {arguments}) => SecondPage(),
    '/thirdPage': (context, {arguments}) => ThirdPage(arguments: arguments),
  };

  @override
  Widget build(BuildContext context) {
    // 常用固定寫法,生成Material風格的App
    return MaterialApp(
      title: "路由使用",
      theme: ThemeData(
        // 預設為亮色主題,可以設定[Brightness.dark]變成黑暗模式
        brightness: Brightness.light,
      ),
//      home: HomePage(),
      initialRoute: '/', // 預設介面
      // 當頁面跳轉時進行引數處理
      onGenerateRoute: (RouteSettings settings) {
        // 獲取宣告的路由頁面函式
        var pageBuilder = routes[settings.name];
        if (pageBuilder != null) {
          if (settings.arguments != null) {
            // 建立路由頁面並攜帶引數
            return MaterialPageRoute(
                builder: (context) =>
                    pageBuilder(context, arguments: settings.arguments));
          } else {
            return MaterialPageRoute(
                builder: (context) => pageBuilder(context));
          }
        }
        return MaterialPageRoute(builder: (context) => HomePage());
      },
    );
  }
}
複製程式碼

第二個頁面傳遞引數時使用Navigator.pushNamed方法,具體程式碼如下:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  // 定義一個需要變數, 接收傳遞的引數
  final String title;

  // 為title設定一個預設引數,這樣的跳轉該介面時可以不傳值。
  SecondPage({Key key, this.title = "第二個介面"});

  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第二個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第二個介面,點選我進入第三個介面");
            // 通過arguments指定引數
            Navigator.pushNamed(context, "/thirdPage",
                arguments: {'title': "命令路由傳遞過來的title"});
          },
        ),
      ),
    );
  }
}

複製程式碼

第三個頁面獲取引數,完整程式碼如下:

import 'package:flutter/material.dart';

class ThirdPage extends StatelessWidget {

  final Map arguments;

  // 為title設定一個預設引數,這樣的跳轉該介面時可以不傳值。
  ThirdPage({Key key, this.arguments});

  @override
  Widget build(BuildContext context) {
    // 通過Scaffold可以方便的生成一個Material風格的頁面
    return Scaffold(
      // 頂部導航欄
      appBar: AppBar(
        title: Text("${arguments != null ? arguments['title'] : "ThirdPage"}"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("我是第三個介面,點選我進入第二個介面"),
          onPressed: () {
            print("我是第三個介面,點選我返回到第二個介面");
            // 返回上一個介面
            Navigator.pop(context, "返回傳遞資料Page3");
          },
        ),
      ),
    );
  }
}
複製程式碼

命名路由傳參優化

上面我們已經實現了引數的傳遞,但是routes頁面列表和onGenerateRoute比較固定,我們能夠把這兩個單獨提取出來成為 一個單獨的類,這樣後期再建立頁面或者維護的時候只需要修改這一個類就行了。

我們新建一個PageConstants類,進行提取,修改後的程式碼如下:

import 'package:flutter/material.dart';

// 引入頁面路徑
import '../pages/HomePage.dart';
import '../pages/SecondPage.dart';
import '../pages/ThirdPage.dart';

// 宣告所有頁面
final routes = {
  '/': (context, {arguments}) => HomePage(),
  '/secondPage': (context, {arguments}) => SecondPage(),
  '/thirdPage': (context, {arguments}) => ThirdPage(arguments: arguments),
};

// 處理引數傳遞
// ignore: top_level_function_literal_block
var onGenerateRoute = (RouteSettings settings) {
  // 獲取宣告的路由頁面函式
  var pageBuilder = routes[settings.name];
  if (pageBuilder != null) {
    if (settings.arguments != null) {
      // 建立路由頁面並攜帶引數
      return MaterialPageRoute(
          builder: (context) =>
              pageBuilder(context, arguments: settings.arguments));
    } else {
      return MaterialPageRoute(
          builder: (context) => pageBuilder(context));
    }
  }
  return MaterialPageRoute(builder: (context) => HomePage());
};
複製程式碼

這時候我們只需要簡單修改MyApp元件即可:

import 'package:flutter/material.dart';
import 'package:hello_flutter/pages/PageConstants.dart';

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // 常用固定寫法,生成Material風格的App
    return MaterialApp(
      title: "路由使用",
      theme: ThemeData(
        // 預設為亮色主題,可以設定[Brightness.dark]變成黑暗模式
        brightness: Brightness.light,
      ),
      initialRoute: '/', // 預設介面
      // 通過PageConstants引入
      onGenerateRoute: onGenerateRoute,
    );
  }
}
複製程式碼

這樣來看就會清爽很多。

篇幅所限,這次的內容就先講到這裡,下篇文章繼續講往後的內容,應該會單獨講一講實現仿閒魚底部tab頁面切換和仿頭條多tab頁切換。

為了第一時間獲取最新文章,請關注公眾號 -- 程式設計師指北,每一個關注都能讓作者多搬一塊磚。

公眾號二維碼

相關文章