天天看點

使用 GoRouter 進行 Flutter 導航:Go 與 Push

作者:堅果

華為雲享專家,InfoQ簽約作者,阿裡雲專家部落客,51CTO部落格專家,?開源項目GVA成員之一,OpenHarmony布道師,專注于大前端技術的分享,包括Flutter,小程式,安卓,VUE,JavaScript。

在使用 GoRouter 進行聲明式路由時,深入解釋 Go 和 Push 的差別

go_router 包是用于聲明式路由的流行包。它基于 Navigator 2.0 API,目的是使用聲明式路由來降低複雜性,無論您的目标平台是什麼(移動、Web、桌面),處理來自 Android、iOS 和 Web 的深度和動态連結,以及其他一些導航相關的場景,同時(希望)提供易于使用的開發人員體驗。當然所有這些都背後一個易于使用的 API。

如果您來自 Navigator 1.0,您将熟悉将路由推送到導航堆棧的概念。

但是在使用 GoRouter 時,您有兩個單獨的選項:

  • go
  • push

本文将探讨兩者的差別,以便您根據具體情況選擇最合适的一種。

GoRouter 的聲明式路由

首先,讓我們考慮一個簡單的路由層次結構,它由一個頂級路由和兩個子路由組成:

GoRouter(
  initialLocation: '/',
  routes: [
    // top-level route
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        // one sub-route
        GoRoute(
          path: 'detail',
          builder: (context, state) => const DetailScreen(),
        ),
        // another sub-route
        GoRoute(
          path: 'modal',
          pageBuilder: (context, state) => const MaterialPage(
            fullscreenDialog: true,
            child: ModalScreen(),
          ),
        )
      ],
    ),
  ],
)
           

複制

讓我們為我們的路線定義 3 個頁面:

使用 GoRouter 進行 Flutter 導航:Go 與 Push

首頁、詳細資訊和model頁面

從頂部路線導航

現在,假設我們在

HomeScreen

中,這隻是一個帶有三個按鈕的簡單頁面,回調定義如下:

// onPressed callback for the first button
context.go('/detail'),
// onPressed callback for the second button
context.push('/detail'),
// onPressed callback for the third button
context.go('/modal'),
           

複制

第一個和第二個回調具有相同的目标位置(

/detail

),是以它們的行為方式相同。

也就是說,在這兩種情況下,我們都會在導航堆棧中得到兩條路線(home → detail)。

使用 GoRouter 進行 Flutter 導航:Go 與 Push

從首頁到詳情頁

Go 和 Push 的差別

從詳細資訊頁面,我們現在可以通過

/modal

兩種不同的方式導航到:

// onPressed callback for the first button
context.go('/modal'),
// onPressed callback for the second button
context.push('/modal'),
           

複制

使用 GoRouter 進行 Flutter 導航:Go 與 Push

**

這一次的結果不同:

  • 如果我們使用

    go

    ,我們最終會在首頁頂部顯示模态頁面
  • 如果我們使用

    push

    ,我們最終會在詳細資訊頁面的頂部出現模态頁面
使用 GoRouter 進行 Flutter 導航:Go 與 Push

Go 和 Push 如何影響導航堆棧

go 通過丢棄之前的路由(/detail)跳轉到目标路由(/modal),因為 /modal 不是 /detail 的子路由:

使用 GoRouter 進行 Flutter 導航:Go 與 Push

img

具有 3 條路線的路線層次結構:請注意,modal 不是詳細的子路線

同時,

push

總是将目标路由添加到現有路由之上,保留導航堆棧。

這意味着一旦我們關閉模态頁面,我們将導航回:

  • 如果我們使用

    go

    ,傳回首頁,
  • 如果我們使用

    push

    ,傳回詳細資訊頁面

這是一個顯示此行為的簡短示範:

使用 GoRouter 進行 Flutter 導航:Go 與 Push

go vs push 路由:動畫視訊

最後附上完整源代碼:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  GoRouter.setUrlPathStrategy(UrlPathStrategy.path);
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final goRouter = GoRouter(
      debugLogDiagnostics: true,
      initialLocation: '/',
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const HomeScreen(),
          routes: [
            GoRoute(
              path: 'detail',
              builder: (context, state) => const DetailScreen(),
            ),
            GoRoute(
              path: 'modal',
              pageBuilder: (context, state) => const MaterialPage(
                fullscreenDialog: true,
                child: ModalScreen(),
              ),
            )
          ],
        ),
      ],
    );
    return MaterialApp.router(
      routerDelegate: goRouter.routerDelegate,
      routeInformationParser: goRouter.routeInformationParser,
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.amber,
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
        backgroundColor: Colors.red,
      ),
      backgroundColor: Colors.red,
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Center(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton(
                onPressed: () => context.go('/detail'),
                child: const CenteredText('go /detail'),
              ),
              const SizedBox(
                height: 32,
              ),
              ElevatedButton(
                onPressed: () => context.push('/detail'),
                child: const CenteredText('push /detail'),
              ),
              const SizedBox(
                height: 32,
              ),
              ElevatedButton(
                onPressed: () => context.go('/modal'),
                child: const CenteredText('go /modal'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail Page'),
        backgroundColor: Colors.green,
      ),
      backgroundColor: Colors.green,
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Center(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton(
                onPressed: () => context.go('/modal'),
                child: const CenteredText('go /modal'),
              ),
              const SizedBox(
                height: 32,
              ),
              ElevatedButton(
                onPressed: () => context.push('/modal'),
                child: const CenteredText('push /modal'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class ModalScreen extends StatelessWidget {
  const ModalScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Modal Page'),
        backgroundColor: Colors.blue,
      ),
      backgroundColor: Colors.blue,
    );
  }
}

class CenteredText extends StatelessWidget {
  const CenteredText(this.text, {Key? key}) : super(key: key);
  final String text;
  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: const TextStyle(fontSize: 30),
      textAlign: TextAlign.center,
    );
  }
}
           

複制

結論

将 go 視為跳到新路線的一種方式。如果新路由不是舊路由的子路由,這将修改底層導航堆棧。

另一方面,push 将始終将目标路由推送到現有導航堆棧的頂部。

有關 GoRouter 的更多資訊,請務必檢視官方文檔。