Flutter之路由和导航

目录:

1、flutter路由和导航简介

Flutter 提供了一个完整的系统,用于在屏幕之间导航和处理 深层链接。没有复杂深度链接的小型应用程序可以使用 Navigator,而具有特定深度链接和导航的应用程序 要求还应该使用 Router 来正确处理 Android 和 iOS,并在应用程序运行时与地址栏保持同步 在 Web 上运行。

2、路由的使用

小组件使用正确的过渡将屏幕显示为堆栈 动画。要导航到新屏幕,请通过 route 访问 并调用命令式方法。

dart 复制代码
child: const Text('Open second screen'),
onPressed: () {
  Navigator.of(context).push(
    MaterialPageRoute(builder: (context) => const SecondScreen()),
  );
},

因为会保留一堆对象(表示历史记录 stack),该方法还接受一个 object。

2.2、使用命名路由

dart 复制代码
child: const Text('Open second screen'),
onPressed: () {
  Navigator.pushNamed(context, '/second');
},

/second表示在列表中声明的命名路由。有关完整示例,请按照 Flutter 说明书中的使用命名路由导航 recipe 进行作。MaterialApp.routes

局限性:

  • 尽管命名路由可以处理深度链接,但行为始终相同,并且 无法自定义。当平台收到新的深度链接时,Flutter 将新用户推送到Navigator 上,而不管用户当前位于何处。
  • Flutter 也不支持使用 命名路由。由于这些原因,我们不建议在大多数 应用。

2.3、使用路由器

具有高级导航和路由要求(例如 使用指向每个屏幕的直接链接的 Web 应用程序,或具有多个小组件的应用程序)应使用路由包,例如 go_router 解析路由路径并配置每当应用程序收到 new deep link 的 intent 值。

要使用 Router,请切换到 or 上的构造函数,并为其提供一个配置。路由包, 如 go_router,通常提供路由配置和路由 可以按如下方式使用:

dart 复制代码
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),

因为像 go_router 这样的包是声明性的,所以它们将始终显示 收到深度链接时的相同屏幕。

go_router的使用案例:

dart 复制代码
dependencies:
  flutter:
    sdk: flutter
  go_router: ^x.y.z  # 替换x.y.z为最新版本号
dart 复制代码
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
 
void main() {
  runApp(MyApp());
}
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _router = GoRouter(
      initialLocation: '/',
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => HomePage(),
        ),
        GoRoute(
          path: '/details',
          builder: (context, state) => DetailsPage(),
        ),
      ],
    );
 
    return MaterialApp.router(
      routerConfig: _router,
    );
  }
}
  1. 创建页面和导航
    确保你已创建了相应的页面(如HomePage和DetailsPage),这些将作为路由的目标。例如:
dart 复制代码
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.go('/details'); // 使用context.go进行导航
          },
          child: Text('Go to Details'),
        ),
      ),
    );
  }
}
  1. 使用参数和嵌套路由(可选)
    你可以在路由中使用参数,并设置嵌套路由:
dart 复制代码
GoRoute(
  path: '/user/:id', // 参数使用冒号标记
  builder: (context, state) {
    final userId = state.params['id']; // 获取参数值
    return UserPage(userId: userId); // 将参数传递给页面
  },
  routes: [ // 嵌套路由示例
    GoRoute(
      path: 'profile', 
      builder: (context, state) => ProfilePage(), 
    ),
  ],
),

在页面中使用参数:

dart 复制代码
class UserPage extends StatelessWidget {
  final String userId; // 页面接收参数构造函数或初始化列表赋值方式之一
  // 构造函数接收参数userId 初始化列表赋值方式也行 如 final String userId; UserPage({Key? key, required this.userId}) : super(key: key); 然后在build中使用userId。 例如显示在文本中:Text('User ID: $userId')。 嵌套路由的使用同理,你可以在ProfilePage中通过context访问嵌套路由的参数。 例如:final profileId = state.params['profileId']; 
  // 在ProfilePage的build方法中使用。 注意这里的state是从context获取的,通常在build方法中通过ModalRoute.of(context).settings来获取。 但由于我们使用了GoRouter,可以直接通过builder的state参数获取。 例如在ProfilePage的build方法中可以直接使用String profileId = state.params['profileId']; 来获取参数。 若要导航到嵌套路由,可以使用context.go('/user/${userId}/profile');。 若要在嵌套路由的页面中使用非嵌套路由的参数,可以通过ModalRoute.of(context).settings.arguments来获取。 但由于我们使用了GoRouter,可以直接通过state参数获取。 若要在嵌套路由中导航,可以使用context.go('profile');(注意没有前导斜杠)。 若要在非嵌套路由中使用嵌套路由的参数,可以在非嵌套路由的页面中通过context.go('/user/${userId}'); 然后在这个路由的builder方法中通过state.subloc获取嵌套路由的状态,例如:String
  UserPage({required this.userId}); 

3、应用中添加Tab导航

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const TabBarDemo());
}

class TabBarDemo extends StatelessWidget {
  const TabBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: const Text('Tabs Demo'),
          ),
          body: const TabBarView(
            children: [
              Icon(Icons.directions_car),
              Icon(Icons.directions_transit),
              Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}

4、页面跳转一个新页面和回退

  • 用 Navigator.push() 跳转到第二个路由
  • 用 Navigator.pop() 回退到第一个路由

5、传递数据到新页面


dart 复制代码
import 'package:flutter/material.dart';

class Todo {
  final String title;
  final String description;

  const Todo(this.title, this.description);
}

void main() {
  runApp(
    MaterialApp(
      title: 'Passing Data',
      home: TodosScreen(
        todos: List.generate(
          20,
          (i) => Todo(
            'Todo $i',
            'A description of what needs to be done for Todo $i',
          ),
        ),
      ),
    ),
  );
}

class TodosScreen extends StatelessWidget {
  const TodosScreen({super.key, required this.todos});

  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            // When a user taps the ListTile, navigate to the DetailScreen.
            // Notice that you're not only creating a DetailScreen, you're
            // also passing the current todo through to it.
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DetailScreen(todo: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});

  // Declare a field that holds the Todo.
  final Todo todo;

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}

6、使用 RouteSettings 传递参数

dart 复制代码
import 'package:flutter/material.dart';

class Todo {
  final String title;
  final String description;

  const Todo(this.title, this.description);
}

void main() {
  runApp(
    MaterialApp(
      title: 'Passing Data',
      home: TodosScreen(
        todos: List.generate(
          20,
          (i) => Todo(
            'Todo $i',
            'A description of what needs to be done for Todo $i',
          ),
        ),
      ),
    ),
  );
}

class TodosScreen extends StatelessWidget {
  const TodosScreen({super.key, required this.todos});

  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            // When a user taps the ListTile, navigate to the DetailScreen.
            // Notice that you're not only creating a DetailScreen, you're
            // also passing the current todo through to it.
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const DetailScreen(),
                  // Pass the arguments as part of the RouteSettings. The
                  // DetailScreen reads the arguments from these settings.
                  //通过此处来传递参数,其他地方同上
                  settings: RouteSettings(arguments: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final todo = ModalRoute.of(context)!.settings.arguments as Todo;

    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
相关推荐
亚洲小炫风7 小时前
flutter 中各种日志
前端·flutter·日志·log
louisgeek9 小时前
Flutter 动画之 Implicit 隐式动画
flutter
勤劳打代码10 小时前
游刃有余 —— Isolate 轻量化实战
flutter·github·dart
RichardLai8815 小时前
[Flutter 基础] - Flutter基础组件 - Text
android·flutter
Ya-Jun1 天前
常用第三方库精讲:cached_network_image图片加载优化
android·flutter
WDeLiang1 天前
Flutter 环境搭建
flutter·ios·visual studio code
程一个大前端1 天前
【Flutter高效开发】GetX指南:一文学会状态管理、路由与依赖注入
flutter
DEVIL1 天前
Flutter中各类Controller的本质
android·flutter
ak啊1 天前
Flutter UI 组件应用一:布局、交互、动画与弹窗
flutter