今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

Flutter佈局基礎——頁面導航和返回

Flutter 佈局基礎 —— 頁面導航和傳值#

說到導航,最常見就是類似於 iOS 中導航控制器的 push 和 pop 效果,同樣 Flutter 中也有類似的效果,使用的就是Navigator組件。

下面,來看一下在 Flutter 中,導航效果Navigator.pushNavigator.pop的使用。

簡單使用#

代碼如下:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '導航演示1',
      home: new FirstScreen(),
    );
  }
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(
        title: Text('導航頁面'),
      ),
      body: Center(
          child: ElevatedButton(
        child: Text('查看商品詳情頁面'),
        onPressed: () {
          Navigator.push(context,
              new MaterialPageRoute(builder: (context) => new SecondScreen()));
        },
      )),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SecondScreen'),
      ),
      body: Center(
        child: OutlinedButton(
          child: Text('返回'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

注意使用Navigator.push時,要跳轉的界面使用MaterialPageRoute包括起來了。
代碼中的SecondScreen添加了一個按鈕,點擊的實現方法中是Navigator.pop,用於返回;但是通常情況下,不需要專門實現Navigator.pop,因為在 iOS 中,當使用了AppBar的時候,會自動在左上角添加返回按鈕;而在安卓中,使用系統返回按鈕也可以直接返回。

使用名字導航#

類似於路由跳轉,使用名字而不是類名進行跳轉。

下面代碼定義了三個界面,MyFirstPage是首頁,MySecondPageMyThirdPage是兩個不同的界面,首頁上面有兩個按鈕,分別對應跳轉MySecondPageMyThirdPage,同時跳轉時傳入參數,顯示在對應頁面中。

代碼如下:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyFirstPage(),
      routes: <String, WidgetBuilder>{
        '/a': (BuildContext context) => MySecondPage(title: 'Page A'),
        'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
      },
    );
  }
}

class MyFirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('首頁'),
      ),
      body: Center(
        child: Column(
          children: [
            OutlinedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/a');
              },
              child: new Text('PageA'),
            ),
            OutlinedButton(
              onPressed: () {
                Navigator.pushNamed(context, 'b');
              },
              child: new Text('PageB'),
            ),
          ],
        ),
      ),
    );
  }
}

class MySecondPage extends StatelessWidget {
  final String title;
  const MySecondPage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('第二頁'),
      ),
      body: Center(
        child: OutlinedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: new Text(this.title),
        ),
      ),
    );
  }
}

class MyThirdPage extends StatelessWidget {
  final String? title;
  const MyThirdPage({Key? key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('第三頁'),
      ),
      body: Center(
        child: new OutlinedButton(
            onPressed: () {
              Navigator.pop(context);
            },
            child: new Text(this.title ?? '默認頁面名稱')),
      ),
    );
  }
}

這裡有幾個點需要注意:

首先是routes的使用,類型為<String, WidgetBuilder>,前者是跳轉頁面的名字和後者是跳轉的對應頁面。而且跳轉頁面的名字中/不是必須的,即可以隨便定義,但按照路由來理解的話,還是建議統一規則命名。

其次是跳轉Navigator的使用,前面直接跳轉的例子中,使用的方法是Navigator.push;而這裡使用的是Navigator.pushNamed

最後需要注意的是頁面傳值。

頁面傳值#

就如同 iOS 開發,頁面傳值分為從上級界面到下級界面的傳值和從下級界面到上級界面的回調傳值。

從上級頁面到下級頁面的傳值#

上面的代碼是從上級頁面到下級頁面到傳值,但MySecondPageMyThirdPage的寫法還不一樣,對比如下:

wecom20210730-172529.png

共有兩個地方不一樣:初始聲明不同,具體使用的不同;

MySecondPage中聲明的title屬性是一個不可空的 String,使用了required修飾 (這個地方要注意,是required而不是@required,有些文章沒有更新),使用的時候直接使用。

MyThirdPage中聲明的title屬性是可空的 String,沒有使用required修飾,但是使用的時候,添加了??提供默認值。

如果弄混了,使用的時候可能會遇到The parameter 'title' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.dart(missing_default_value_for_parameter)這種報錯,原因就是可空和不可空的區別,修改方法就是,一種是選擇聲明為可空類型,使用時加上判斷;一種是使用required修飾,聲明不可空。

從下級頁面到上級頁面的傳值#

Navigator.Push方法是可以有返回值的,而且返回值是Future類型,當調用Navigator.Pop方法時,第二個可選參數傳入了內容,則會在Navigator.Push中返回。

代碼如下:


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyFirstPage(),
      routes: <String, WidgetBuilder>{
        '/a': (BuildContext context) => MySecondPage(title: 'Page A'),
        'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
      },
    );
  }
}

class MyFirstPage extends StatelessWidget {
  String callbackText = '';
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('首頁'),
      ),
      body: Center(
        child: Column(
          children: [
            OutlinedButton(
              onPressed: () {
                _pagePush(context, '/a');
              },
              child: new Text('PageA'),
            ),
            OutlinedButton(
              onPressed: () {
                // Navigator.pushNamed(context, 'b');
                _pagePush(context, 'b');
              },
              child: new Text('PageB'),
            ),
            Text(callbackText),
          ],
        ),
      ),
    );
  }
}

_pagePush(BuildContext context, String target) async {
  final result = await Navigator.pushNamed(context, target);
  ScaffoldMessenger.of(context).showSnackBar(
    new SnackBar(content: Text("$result")),
  );
}

class MySecondPage extends StatelessWidget {
  final String title;
  const MySecondPage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('第二頁'),
      ),
      body: Center(
        child: OutlinedButton(
          onPressed: () {
            Navigator.pop(context, '第二頁回調');
          },
          child: new Text(this.title),
        ),
      ),
    );
  }
}

class MyThirdPage extends StatelessWidget {
  final String? title;
  const MyThirdPage({Key? key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('第三頁'),
      ),
      body: Center(
        child: new OutlinedButton(
            onPressed: () {
              Navigator.pop(context, '第三頁回調');
            },
            child: new Text(this.title ?? '默認頁面名稱')),
      ),
    );
  }
}

效果如下:

pagecallback.gif

注意上面按鈕點擊方法的使用,單獨封裝了一個_pagePush的方法,並且使用async修飾,原因是Navigator.push的返回值是一個Future
類型,需要使用await,而await只能在async修飾的方法中使用,如果寫過 ReactNative 的應該會熟悉這種寫法。

參考#

Navigator Dev Doc
Flutter 免費視頻第四季 - 頁面導航和其他
The parameter can't have a value of 'null' because of its type in Dart
從新頁面返回數據給上個頁面
Future class

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。