Flutter 路由导航完全指南

引言

路由导航是移动应用的核心功能之一。Flutter 提供了强大且灵活的路由导航系统。本文将深入探讨 Flutter 路由导航的各种用法和高级技巧。

基础概念回顾

路由类型

  • MaterialPageRoute: Material风格路由
  • CupertinoPageRoute: iOS风格路由
  • PageRouteBuilder: 自定义路由

基本导航

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondScreen()),
);

Navigator.pop(context);

高级技巧一:命名路由

注册路由

void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) => HomeScreen(),
      '/second': (context) => SecondScreen(),
      '/third': (context) => ThirdScreen(),
    },
  ));
}

使用命名路由

Navigator.pushNamed(context, '/second');
Navigator.pop(context);

高级技巧二:传递参数

方式一:构造函数传参

class SecondScreen extends StatelessWidget {
  final String message;

  const SecondScreen({super.key, required this.message});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(child: Text(message)),
    );
  }
}

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SecondScreen(message: 'Hello from Home'),
  ),
);

方式二:通过 settings 传参

Navigator.pushNamed(
  context,
  '/second',
  arguments: 'Hello from Home',
);

// 在接收页面
final message = ModalRoute.of(context)?.settings.arguments as String;

方式三:传递复杂对象

class User {
  final String name;
  final int age;

  const User({required this.name, required this.age});
}

Navigator.pushNamed(
  context,
  '/profile',
  arguments: const User(name: 'John', age: 30),
);

// 在接收页面
final user = ModalRoute.of(context)?.settings.arguments as User;

高级技巧三:返回数据

返回简单数据

// 在 SecondScreen 中
ElevatedButton(
  onPressed: () {
    Navigator.pop(context, 'Data from Second');
  },
  child: const Text('Return'),
);

// 在 HomeScreen 中
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SecondScreen()),
);
print(result); // 'Data from Second'

返回复杂数据

// 返回 Map
Navigator.pop(context, {'status': 'success', 'data': 'value'});

// 返回自定义对象
Navigator.pop(context, const User(name: 'John', age: 30));

高级技巧四:自定义路由动画

淡入淡出动画

Route fadeRoute(Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = 0.0;
      const end = 1.0;
      const curve = Curves.ease;
      final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
      return FadeTransition(
        opacity: animation.drive(tween),
        child: child,
      );
    },
  );
}

Navigator.push(context, fadeRoute(const SecondScreen()));

滑动动画

Route slideRoute(Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(1.0, 0.0);
      const end = Offset.zero;
      const curve = Curves.ease;
      final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  );
}

缩放动画

Route scaleRoute(Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return ScaleTransition(
        scale: animation,
        child: child,
      );
    },
  );
}

高级技巧五:嵌套导航

Tab 导航

class TabNavigator extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey;
  final Widget child;

  const TabNavigator({
    super.key,
    required this.navigatorKey,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      onGenerateRoute: (settings) {
        return MaterialPageRoute(builder: (context) => child);
      },
    );
  }
}

主页面

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _tab1NavigatorKey = GlobalKey<NavigatorState>();
  final _tab2NavigatorKey = GlobalKey<NavigatorState>();
  int _currentIndex = 0;

  void _onTabTapped(int index) {
    if (index == _currentIndex) {
      // 双击回到根页面
      if (index == 0) {
        _tab1NavigatorKey.currentState?.popUntil((route) => route.isFirst);
      } else {
        _tab2NavigatorKey.currentState?.popUntil((route) => route.isFirst);
      }
    } else {
      setState(() => _currentIndex = index);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: [
          TabNavigator(
            navigatorKey: _tab1NavigatorKey,
            child: const FirstTab(),
          ),
          TabNavigator(
            navigatorKey: _tab2NavigatorKey,
            child: const SecondTab(),
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _onTabTapped,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }
}

实战案例:登录流程

class AuthService {
  static Future<bool> login(String email, String password) async {
    await Future.delayed(const Duration(seconds: 2));
    return email.isNotEmpty && password.isNotEmpty;
  }
}

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;

  Future<void> _login() async {
    setState(() => _isLoading = true);
    
    try {
      final success = await AuthService.login(
        _emailController.text,
        _passwordController.text,
      );
      
      if (success) {
        if (mounted) {
          Navigator.pushReplacementNamed(context, '/home');
        }
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Login failed')),
        );
      }
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _emailController,
              decoration: const InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: _isLoading ? null : _login,
              child: _isLoading 
                  ? const CircularProgressIndicator()
                  : const Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}

实战案例:路由守卫

class AuthGuard extends StatelessWidget {
  final Widget child;

  const AuthGuard({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    final isLoggedIn = AuthService.isLoggedIn;
    
    if (!isLoggedIn) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        Navigator.pushReplacementNamed(context, '/login');
      });
      return const SizedBox();
    }
    
    return child;
  }
}

// 使用
MaterialApp(
  routes: {
    '/home': (context) => const AuthGuard(child: HomeScreen()),
    '/profile': (context) => const AuthGuard(child: ProfileScreen()),
  },
);

实战案例:路由管理类

class AppRouter {
  static Future push(BuildContext context, Widget page) {
    return Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => page),
    );
  }

  static Future pushNamed(BuildContext context, String routeName, {Object? arguments}) {
    return Navigator.pushNamed(context, routeName, arguments: arguments);
  }

  static void pop(BuildContext context, {Object? result}) {
    Navigator.pop(context, result);
  }

  static void popUntilFirst(BuildContext context) {
    Navigator.popUntil(context, (route) => route.isFirst);
  }

  static void replace(BuildContext context, Widget page) {
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) => page),
    );
  }

  static void replaceNamed(BuildContext context, String routeName, {Object? arguments}) {
    Navigator.pushReplacementNamed(context, routeName, arguments: arguments);
  }
}

常见问题与解决方案

Q1:如何返回数据?

A:使用 Navigator.pop(context, data):

Navigator.pop(context, 'result');
final result = await Navigator.push(...);

Q2:如何替换路由?

A:使用 pushReplacement:

Navigator.pushReplacement(context, MaterialPageRoute(...));

Q3:如何返回根页面?

A:使用 popUntil:

Navigator.popUntil(context, (route) => route.isFirst);

最佳实践

1. 使用命名路由

MaterialApp(
  routes: {
    '/': (context) => const HomeScreen(),
    '/second': (context) => const SecondScreen(),
  },
);

2. 统一路由管理

class Routes {
  static const String home = '/';
  static const String second = '/second';
  static const String profile = '/profile';
}

3. 使用类型安全的参数传递

class SecondScreenArguments {
  final String message;
  final int count;

  const SecondScreenArguments({required this.message, required this.count});
}

Navigator.pushNamed(
  context,
  Routes.second,
  arguments: const SecondScreenArguments(message: 'Hello', count: 5),
);

总结

Flutter 的路由导航系统非常强大。通过本文的学习,你应该能够:

  1. 使用基本的导航方法
  2. 使用命名路由
  3. 传递和返回数据
  4. 创建自定义路由动画
  5. 实现嵌套导航
  6. 创建路由守卫

掌握这些技巧,能够帮助你构建更加复杂和用户友好的应用。

更多推荐