Flutter-Navigation

245 阅读4分钟

简单页面跳转

Flutter中跳转页面很简单, 在Flutter中screenspages都被称为routes,主要用到Navigator, 它维护了一个路由栈, push()的时候就是入栈一个Route对象, 其中Route对象主要是由MaterialPageRoute封装而成的, 定义了要怎么去跳转, 完成跳转返回主要是使用了其中两个方法Navigator.push()Navigator.pop()

// 第一个页面
class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("first route"),
      ),
      body: Center(
          child: ElevatedButton(
        child: const Text("open route"),
        onPressed: () {
        // 跳转
          Navigator.push(context,
              MaterialPageRoute(builder: (context) => const SecondRoute()));
        },
      )),
    );
  }
}

// 第二个页面
class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
    // appbar 默认会添加一个返回按钮
      appBar: AppBar(title: const Text("second page")),
      body: Center(
          child: ElevatedButton(
        child: const Text("back to first"),
        onPressed: () {
        // 返回到上一个页面
          Navigator.pop(context);
        },
      )),
    );
  }
}

使用命名(静态)路由进行跳转

上面的例子进行跳转的时候, 都写死了.. 只能跳转到某个页面MaterialPageRoute(builder: (context) => const SecondRoute()), 如果跳转的是同一个页面就会出现很多重复的代码, 要解决这个问题, 就需要使用命名路由Navigator.pushNamed(), 跟上面例子的区别就是, 在MaterialApp的构造器里有一个初始化initialRoute(从哪里开始)和routes(有哪些地址).

tips: 主要差距是在`main()`函数这里
void main() {
  runApp(MaterialApp(
    // home: FirstRoute(),  // 如果定义了routes: 这里就不能再有home, 否则会报错
    initialRoute: '/',
    routes: {
      '/': (context) => const FirstRoute(),
      '/second': (context) => const SecondRoute(),
    },
  ));
}

定义好路由之后, 跳转到SecondRoute就需要用另一个方法

Navigator.pushNamed(context, "/second");

通过命名路由在页面之间传递参数

命名路由貌似官方不推荐? 最大的区别是页面要自己持有routeName 先定义一个传递参数的对象

class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}

第二个页面需要自己持有routeName

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

// 定义route name
  static const routeName = '/second';

  @override
  Widget build(BuildContext context) {
  
  // 获取传递过来的参数
    final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;

    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      body: Center(
          child: ElevatedButton(
        child: Text(args.message),
        onPressed: () {
          Navigator.pop(context);
        },
      )),
    );
  }
}

MaterialApp中的定义也需要进行改变


void main() {
  runApp(MaterialApp(
    // home: FirstRoute(),  // 如果定义了routes: 这里就不能再有home
    initialRoute: '/',
    routes: {
      '/': (context) => const FirstRoute(),
      // '/second': (context) => const SecondRoute(),
      SecondRoute.routeName: (context) => const SecondRoute()
    },
  ));
}

跳转的时候就可以传递参数

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("first route"),
      ),
      body: Center(
          child: ElevatedButton(
        child: const Text("open route"),
        onPressed: () {
          // Navigator.pushNamed(context, "/second");
          Navigator.pushNamed(
            context, 
            SecondRoute.routeName,
              arguments: ScreenArguments("param title", "param message"));
        },
      )),
    );
  }
}

也可以使用onGenerateRoute来传递参数, 这个要依赖RouteSettings

import 'package:flutter/material.dart';

class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}

class FirstRoute extends StatelessWidget {
  static const routeName = "/first";

  const FirstRoute({super.key});

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

    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      body: Center(child: Text(args.message)),
    );
  }
}

class SecondRoute extends StatelessWidget {
  static const routeName = "/second";
  final String title;
  final String message;

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

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Home route")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
                onPressed: () {
                  Navigator.pushNamed(context, FirstRoute.routeName,
                      arguments: ScreenArguments("title", "message"));
                },
                child: Text('navigate to first')),
            SizedBox(
              height: 10,
            ),
            ElevatedButton(
                onPressed: () {
                  Navigator.pushNamed(context, SecondRoute.routeName,
                      arguments: ScreenArguments(
                          "this navi title", "this navi message"));
                },
                child: Text('navigate to second')),
          ],
        ),
      ),
    );
  }
}

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        FirstRoute.routeName: (context) => const FirstRoute(),
      },
      onGenerateRoute: ((settings) {
      // 判断是哪个路由, 然后进行赋值
        if (settings.name == SecondRoute.routeName) {
          final args = settings.arguments as ScreenArguments;

          return MaterialPageRoute(builder: ((context) {
            return SecondRoute(title: args.title, message: args.message);
          }));
        }
      }),
      title: "home route",
      home: const HomeRoute(),
    );
  }
}

从返回的屏幕获取数据

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      title: 'Returning Data',
      home: HomeScreen(),
    ),
  );
}

// Home screen
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Returning Data Demo'),
      ),
      body: const Center(
        child: SelectionButton(),
      ),
    );
  }
}

// tips: 这里继承的是 StatefulWidget 
class SelectionButton extends StatefulWidget {
  const SelectionButton({super.key});

  @override
  State<SelectionButton> createState() => _SelectionButtonState();
}

class _SelectionButtonState extends State<SelectionButton> {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: const Text('Pick an option, any option!'),
    );
  }

  // 异步等待
  Future<void> _navigateAndDisplaySelection(BuildContext context) async {

    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const SelectionScreen()),
    );
    // 这个应该是判断当前页面有没有被渲染(显示)? 
    if (!mounted) return;

    // 显示snackBar的时候, 先把之前的都给隐藏掉
    ScaffoldMessenger.of(context)
      ..removeCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text('$result')));
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () {
                  Navigator.pop(context, 'Yep!');
                },
                child: const Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () {
                  Navigator.pop(context, 'Nope.');
                },
                child: const Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

传递数据给新的页面

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),
            
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const DetailScreen(),
                  // 传递数据的时候还是需要 MaterialPageRoute的包装
                  // 只不过数据包装是要通过 RouteSettings 里面的 arguments来进行的
                  settings: RouteSettings(
                    arguments: todos[index],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
  // 这里接收数据  就是通过 settings.arguments 来做
    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.0),
        child: Text(todo.description),
      ),
    );
  }
}