Skip to content
alex's blog
Go back

Flutter 导航与路由完全指南

编辑页面

导航和路由是移动应用的核心功能。Flutter 提供了多种导航方式,从简单的页面跳转到复杂的深层链接处理。本文将全面介绍 Flutter 中的导航和路由方案。

基础导航 - Navigator

1. 基本导航操作

// 导航到新页面
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondPage()),
);

// 返回上一页
Navigator.pop(context);

// 返回并传递数据
Navigator.pop(context, '返回的数据');

// 替换当前页面
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => SecondPage()),
);

// 清除所有路由并导航到新页面
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => HomePage()),
  (route) => false, // 移除所有之前的路由
);

2. 传递数据

// 传递数据到新页面
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => SecondPage(data: 'Hello'),
  ),
);

// 接收返回的数据
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondPage()),
);

if (result != null) {
  print('收到返回数据: $result');
}

3. 自定义路由动画

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
    transitionDuration: Duration(milliseconds: 300),
  ),
);

命名路由

1. 定义路由

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/second': (context) => SecondPage(),
        '/third': (context) => ThirdPage(),
      },
    );
  }
}

2. 使用命名路由

// 导航
Navigator.pushNamed(context, '/second');

// 传递参数(需要手动处理)
Navigator.pushNamed(
  context,
  '/second',
  arguments: {'id': 123, 'name': 'Flutter'},
);

3. 接收参数

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
    
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Text('ID: ${args['id']}, Name: ${args['name']}'),
    );
  }
}

go_router - 现代化路由方案

go_router 是 Flutter 官方推荐的现代化路由解决方案:

1. 基础配置

import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/second',
      builder: (context, state) => SecondPage(),
    ),
    GoRoute(
      path: '/user/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return UserPage(userId: id);
      },
    ),
  ],
);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

2. 路径参数

GoRoute(
  path: '/user/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    return UserPage(userId: id);
  },
);

// 使用
context.go('/user/123');

3. 查询参数

GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['q'] ?? '';
    return SearchPage(query: query);
  },
);

// 使用
context.go('/search?q=flutter');

4. 嵌套路由

final router = GoRouter(
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return ScaffoldWithNavBar(navigationShell: navigationShell);
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => HomePage(),
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/profile',
              builder: (context, state) => ProfilePage(),
            ),
          ],
        ),
      ],
    ),
  ],
);

5. 重定向和守卫

final router = GoRouter(
  redirect: (context, state) {
    final isLoggedIn = AuthService.isLoggedIn;
    final isGoingToLogin = state.matchedLocation == '/login';
    
    if (!isLoggedIn && !isGoingToLogin) {
      return '/login';
    }
    
    if (isLoggedIn && isGoingToLogin) {
      return '/';
    }
    
    return null; // 不重定向
  },
  routes: [
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginPage(),
    ),
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
  ],
);

6. 错误处理

final router = GoRouter(
  errorBuilder: (context, state) => ErrorPage(error: state.error),
  routes: [
    // ...
  ],
);

底部导航栏

1. 使用 IndexedStack

class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;
  
  final List<Widget> _pages = [
    HomePage(),
    SearchPage(),
    ProfilePage(),
  ];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

2. 使用 go_router 的 StatefulShellRoute

final router = GoRouter(
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return ScaffoldWithNavBar(navigationShell: navigationShell);
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => HomePage(),
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/search',
              builder: (context, state) => SearchPage(),
            ),
          ],
        ),
      ],
    ),
  ],
);

深层链接(Deep Linking)

1. 配置 Android

<!-- android/app/src/main/AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="myapp"/>
    </intent-filter>
</activity>

2. 配置 iOS

<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

3. 处理深层链接

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/product/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return ProductPage(productId: id);
      },
    ),
  ],
);

// 处理应用启动时的链接
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 处理初始链接
  final initialLink = await getInitialLink();
  if (initialLink != null) {
    // 处理深层链接
  }
  
  runApp(MyApp());
}

路由过渡动画

1. 自定义过渡

GoRoute(
  path: '/second',
  pageBuilder: (context, state) => CustomTransitionPage(
    key: state.pageKey,
    child: SecondPage(),
  ),
);

class CustomTransitionPage extends CustomTransitionPage {
  const CustomTransitionPage({
    required super.key,
    required super.child,
  });
  
  @override
  Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return SlideTransition(
      position: animation.drive(
        Tween(begin: Offset(1.0, 0.0), end: Offset.zero)
            .chain(CurveTween(curve: Curves.ease)),
      ),
      child: child,
    );
  }
}

最佳实践

  1. 使用 go_router:对于新项目,推荐使用 go_router
  2. 类型安全:使用路径参数而不是查询参数传递关键数据
  3. 错误处理:始终处理路由错误
  4. 性能优化:使用 IndexedStack 保持页面状态
  5. 深层链接:从一开始就考虑深层链接支持

常见问题

1. 如何防止返回按钮?

// 使用 WillPopScope
WillPopScope(
  onWillPop: () async {
    // 返回 false 阻止返回
    return false;
  },
  child: Scaffold(...),
)

2. 如何清除路由栈?

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => HomePage()),
  (route) => false,
);

3. 如何获取当前路由?

// 使用 go_router
final currentLocation = GoRouter.of(context).routerDelegate.currentConfiguration.uri.path;

// 使用 Navigator
final route = ModalRoute.of(context);

总结

Flutter 提供了多种导航方案:

选择路由方案时,要考虑:

掌握这些导航技巧,能帮助你构建更好的 Flutter 应用。


导航和路由是 Flutter 应用的基础,选择合适的方案能大大提升开发效率和用户体验。


编辑页面
Share this post on:

Previous Post
Flutter 性能优化实战指南
Next Post
Flutter 状态管理完整指南