Flutter之Router和Navigator实现页面跳转

大多数应用程序都具有多个Page或View,并且希望将用户从当前Page平滑过渡到另一个页面。Flutter的路由和导航功能可帮助您管理应用中屏幕之间的命名和过渡。

概述

管理多个页面时有两个核心概念和类:Route和 Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。

  • 从First页面导航到second页面并返回FIrst页面:

      void main() => runApp(new MaterialApp(
      title: 'Navigation Basics',
      home: FirstRoute(),
      ));
    
      class FirstRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
      return Scaffold(
      appBar: AppBar(
      title: Text('First Route'),
      ),
      body: Center(
      child: RaisedButton(
        child: Text('Open route'),
        onPressed: () {
          // Navigate to second route when tapped.
          Navigator.of(context).push(new MaterialPageRoute(
              builder: (BuildContext context) => SecondRoute()));
        },
      ),
      ),
      );
      }
      }
    
      class SecondRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
      return Scaffold(
      appBar: AppBar(
      title: Text("Second Route"),
      ),
       body: Center(
      child: RaisedButton(
        onPressed: () {
          // Navigate back to first route when tapped.
          Navigator.pop(context);
        },
        child: Text('Go back!'),
      ),
      ),
      );
      }
      }
    
  • 从A页面发送数据去B的页面,实际上是通过构造函数传递数据:

    class Todo {
        final String title;
        final String describe;

        Todo(this.title, this.describe);
    }

    class TodosScreen extends StatelessWidget {
            final List<Todo> todos;

            const TodosScreen({Key key, this.todos}) : super(key: key);

        @override
        Widget build(BuildContext context) {
        return new Scaffold(
            appBar: new AppBar(
            title: new Text('Todos'),
        ),
        body: new ListView.builder(
            itemCount: todos.length,
      itemBuilder: (BuildContext context, int index) {
        return new ListTile(
          title: new Text('todos[index].title'),
          onTap: () {
            Navigator.push(
                context,
                new MaterialPageRoute(
                    builder: (BuildContext context) => new DetailScreen(
                          todo: todos[index],
                        )));
          },
        );
      }),
    );
    }
    }

    class DetailScreen extends StatelessWidget {
            final Todo todo;

            const DetailScreen({Key key, this.todo}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return new Scaffold(
        appBar: new AppBar(
        title: new Text('${todo.title}'),
        ),
        body: new Padding(
        padding: EdgeInsets.all(16.0),
    child: new Text(todo.describe),
      ),
     );
    }
    }
  • 等待其他页面返回数据
    class HomeScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: Text('Returning Data Demo'),
    ),
    body: Center(child: SelectionButton()),
    );
    }
    }

    class SelectionButton extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return RaisedButton(
    onPressed: () {
    _navigateAndDisplaySelection(context);
     },
    child: Text('Pick an option, any option!'),
    );
    }

    ///运行SelectionScreen页面并且等待SelectionScreen Navigator.pop!返回结果
    void _navigateAndDisplaySelection(BuildContext context) async {
    ///Navigator.push 将在我们调用SelectionScreen页面的Navigator.pop完成后返回一个携带结果数据Future
    final result =
    await Navigator.push(context, new MaterialPageRoute(builder: (context) {
     return SelectionScreen();
    }));

    //    String valResult = await result;

    ///之后等待SelectionScreen页面返回结果,隐藏任意上一个的SnackBars,并显示并将结果显示在SnackBar
    Scaffold.of(context)
        ..removeCurrentSnackBar()
        ..showSnackBar(new SnackBar(content: new Text(result)));
             }
            }

    class SelectionScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    appBar: AppBar(
    title: Text('Pick an option'),
    ),
    body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: RaisedButton(
            onPressed: () {
              // Close the screen and return "Yep!" as the result
              Navigator.pop(context, 'Yep!');
            },
            child: Text('Yep!'),
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: RaisedButton(
            onPressed: () {
              // Close the screen and return "Nope!" as the result
              Navigator.pop(context, 'Nope.');
            },
            child: Text('Nope.'),
          ),
        )
      ],
    ),
        ),
        );
    }
    }
  • 通过名字路由到其他页面
    void main() {
    runApp(MaterialApp(
    title: 'Named Routes Demo',
     // Start the app with the "/" named route. In our case, the app will start
     // on the FirstScreen Widget
     initialRoute: '/',
        routes: {
        // When we navigate to the "/" route, build the FirstScreen Widget
        '/': (context) => FirstScreen(),
        // When we navigate to the "/second" route, build the SecondScreen Widget
         '/second': (context) => SecondScreen(),
     },
    ));
    }

    class FirstScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: Text('First Screen'),
        ),
        body: Center(
    child: RaisedButton(
      child: Text('Launch screen'),
      onPressed: () {
        // Navigate to the second screen using a named route
        Navigator.pushNamed(context, '/second');
      },
    ),
         ),
        );
    }
    }

    class SecondScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: Text("Second Screen"),
        ),
        body: Center(
    child: RaisedButton(
      onPressed: () {
        // Navigate back to the first screen by popping the current route
        // off the stack
        Navigator.pop(context);
      },
      child: Text('Go back!'),
    ),
        ),
     );
    }
    }
  • 跳转页面添加过度动画
    class HeroApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Transition Demo',
    home: MainScreen(),
    );
    }
    }

    class MainScreen extends StatelessWidget {

    @override
    Widget build(BuildContext context) {
        return Scaffold(
    appBar: AppBar(
    title: Text('Main Screen'),
    ),
    body: GestureDetector(
    child: Hero(
      tag: 'imageHero',
      child: Image.network(
        'http://pic37.nipic.com/20140113/8800276_184927469000_2.png',
      ),
    ),

    ///transitionsBuilder
    ///I/flutter (21575): pageBuilder
    ///I/flutter (21575): transitionsBuilder
    ///I/flutter (21575): transitionsBuilder
    ///I/flutter (21575): transitionsBuilder
    onTap: () {
      Navigator.push(
          context,
          new PageRouteBuilder(
              transitionDuration: new Duration(seconds: 2),
              transitionsBuilder: (BuildContext context,
                  Animation<double> animation,
                  Animation<double> secondaryAnimation,
                  Widget child) {
                print('transitionsBuilder  这里会一直执行到动画结束');
    //                    return SlideTransition(
    //                      position: new Tween<Offset>(
    //                        begin: const Offset(0.0, 1.0),
    //                        end: Offset.zero,
    //                      ).animate(animation),
    //                      child: SlideTransition(
    //                        position: Tween<Offset>(
    //                          begin: Offset.zero,
    //                          end: const Offset(0.0, 1.0),
    //                        ).animate(secondaryAnimation),
    //                        child: child,
    //                      ),
    //                    );

                return SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(0.0, 1.0),
                    end: Offset.zero,
                  ).animate(animation),
                  child: child, // child is the value returned by pageBuilder
                );
              },
              pageBuilder: (BuildContext context,
                  Animation<double> animation,
                  Animation<double> secondaryAnimation) {
                print('pageBuilder');
                return new DetailScreen();
              }));
    },
    ),
    );
    }
    }

    class DetailScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
     body: GestureDetector(
    child: Center(
      child: Hero(
        tag: 'imageHero',
        child: Image.network(
          'http://pic37.nipic.com/20140113/8800276_184927469000_2.png',
        ),
      ),
    ),
    onTap: () {
      Navigator.pop(context);
    },
     ),
     );
    }
    }
  • 自定义Router

    class CustomRoute extends PageRouteBuilder {
      final Widget widget;
    
    CustomRoute(this.widget)
    : super(
          // 设置过度时间
          transitionDuration: Duration(seconds: 1),
          // 构造器
          pageBuilder: (
            // 上下文和动画
            BuildContext context,
            Animation<double> animaton1,
            Animation<double> animaton2,
          ) {
            return widget;
          },
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animaton1,
            Animation<double> animaton2,
            Widget child,
          ) {
    //              旋转加缩放动画效果
            return RotationTransition(
              turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
                parent: animaton1,
                curve: Curves.fastOutSlowIn,
              )),
              child: ScaleTransition(
                scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
                    parent: animaton1, curve: Curves.fastOutSlowIn)),
                child: child,
              ),
            );
          });
    }
    

使用很简单:

   final result =
    await Navigator.push(context, new CustomRoute(SelectionScreen()));

//    String valResult = await result;

///之后等待SelectionScreen页面返回结果,隐藏任意上一个的SnackBars,并显示并将结果显示在SnackBar
Scaffold.of(context)
  ..removeCurrentSnackBar()
  ..showSnackBar(new SnackBar(content: new Text(result)));

实际项目中使用配置和配置:

void main() {
  var widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  widgetsBinding.addObserver(AppObserver());

  ///在当前 Frame 绘制完成后回调,并且只会回调一次
widgetsBinding.addPostFrameCallback((duration) {
print('onPostFrameCallback>>${duration.toString()}');
});

 ///会在每次绘制 Frame 结束后回调,可用来监测 FPS
  widgetsBinding.addPersistentFrameCallback((timeStamp) {});

  runApp(const TripApp());
}

class AppObserver with WidgetsBindingObserver {
// 页面 pop
@override
Future<bool> didPopRoute() => Future<bool>.value(false);

  // 页面 push
  @override
  Future<bool> didPushRoute(String route) => Future<bool>.value(false);

// 系统窗口相关改变回调,如旋转
  @override
  void didChangeMetrics() {
    print('didChangeMetrics');
  }

  // 文本缩放系数变化
  @override
  void didChangeTextScaleFactor() {
print('didChangeTextScaleFactor');
  }

  // 系统亮度变化
  @override
  void didChangePlatformBrightness() {
print('didChangePlatformBrightness');
  }

    // 本地化语言变化
  @override
  void didChangeLocales(List<Locale>? locale) {
print('didChangeLocales');
  }

  // App 生命周期变化
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('didChangeAppLifecycleState>>${state.name}');
  }

  // 内存警告回调
  @override
  void didHaveMemoryPressure() {
print('didHaveMemoryPressure');
  }

  // Accessibility 相关特性回调
  @override
  void didChangeAccessibilityFeatures() {
print('didChangeAccessibilityFeatures');
  }

    // 应用程序请求退出时调用
  // 在这里执行退出前的清理工作
  @override
  Future<AppExitResponse> didRequestAppExit() {
    return super.didRequestAppExit();
  }
}

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

  @override
  State<TripApp> createState() => _TripAppState();
}

class _TripAppState extends State<TripApp> {
  @override
  Widget build(BuildContext context) {
    ScreenAdaptHelper.init(context);
    return MaterialApp(
      title: 'trip flutter',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
    useMaterial3: false,
  ),
  //默认为launcher,App启动的页面
  initialRoute: RoutersManager.launcher,

  /// 注册路由表
  routes: RoutersManager.registerRouter(context),

  /// 动态路由,如果有些页面不在路由表中,则使用此方法进行跳转
  onGenerateRoute: (RouteSettings settings) =>
      RoutersManager.generateRoute(settings),
);
  }
}

路由管理

class RoutersManager {
  static const String login = "/login";
  static const String register = "/register";
  static const String home = "/home";
  static const String launcher = "/launcher";
  static const String main = "/man";

.....此处省略一万字.........

  static Map<String, WidgetBuilder> registerRouter(BuildContext context) {
return {
  launcher: (context) => const LauncherPage(),
  home: (context) => const HomePage(),
  login: (context) => const LoginPage(),
  register: (context) => const RegisterPage(),
  main: (context) => const AppMainPage(),
};
  }

  ///onGenerateRoute
  ///如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;
  ///如果路由表中没有注册,才会调用onGenerateRoute来生成路由。
  static Route<dynamic>? generateRoute(RouteSettings settings) {
switch (settings.name) {
  case launcher:
    return MaterialPageRoute(
        builder: (context) => const LauncherPage(), settings: settings);
  case main:
    return MaterialPageRoute(
        builder: (context) => const AppMainPage(), settings: settings);
  case home:
    return MaterialPageRoute(
        builder: (context) => const HomePage(), settings: settings);
  case login:
    return MaterialPageRoute(
        builder: (context) => const LoginPage(), settings: settings);
  case register:
    return MaterialPageRoute(
        builder: (context) => const RegisterPage(), settings: settings);
  default:
    return null;
}
  }

  ///pop 将栈顶的页面弹出
  static void back(BuildContext context) => Navigator.pop(context);

    ///pushReplacementNamed 跳转登陆页面 ,就是把当前路由栈栈顶的页面替换成 登陆页面
  static Future<dynamic> toLogin(BuildContext context,
      {Map<String, Object>? arguments}) =>
  Navigator.pushNamedAndRemoveUntil(
      context, login, arguments: arguments, (route) => route == null);

  ///pushReplacementNamed 跳转注册页面  就是把当前路由栈栈顶的页面替换成 注册页面
  static Future<dynamic> toRegister(BuildContext context) =>
  Navigator.pushNamed(context, register);

  ///若要删除所有路由,直到具有特定名称的路由为止
  ///pushNamedAndRemoveUntil 跳转注home页面  就是把当前路由栈Home页面之前的页面全部移除
///page 选中Page
  static Future<dynamic> toMainPage(BuildContext context, {int page = 0}) {
Map<String, Object>? arguments = {"page": page};
return Navigator.pushNamedAndRemoveUntil(
    context, main, arguments: arguments, ModalRoute.withName(main));
  }
.....此处省略一万字.........
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容