美文网首页
Flutter(一):Navigator&Router(导航与路

Flutter(一):Navigator&Router(导航与路

作者: Jeremy不爱吃辣 | 来源:发表于2020-09-23 11:03 被阅读0次

    近期在学习flutter,顺手写点东西记录一下学习心得以及踩过的坑。本文只讲解了简单的页面之间的跳转,路由传参等知识点如需了解,自行网上查找。

    1.路由的原理

    大多数应用程序具有多个页面或视图,并且希望将用户从页面平滑过渡到另一个页面。Flutter的路由和导航功能可帮助我们管理应用中屏幕之间的命名和过渡,
    管理多个页面时有两个核心概念和类:RouteNavigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。

    2.Android、IOS、Flutter页面跳转比较

    Android实现页面之间的跳转是通过context.startActivity,iOS中使用的是viewController。而在Flutter实现页面之间的跳转与数据传递使用的是Navigator.push和Navigator.pop以及Router,最终效果图如下。


    finalReder.gif

    3.新建页面

    本文主要讲解页面直接的跳转,简单写两个页面就可以了。所以这里我就只贴登录页面和主页面的代码。

    • 登录页面:login_page
    • 主页面:welcome_page

    4.登录页面

    import 'package:flutter/material.dart';
    import 'package:fluttertoast/fluttertoast.dart';
    
    class LoginPage extends StatefulWidget {
      @override
      _LoginPageState createState() {
        return _LoginPageState();
      }
    }
    
    class _LoginPageState extends State<LoginPage> {
      TextEditingController _pwdEditController;
      TextEditingController _userNameEditController;
    
      final FocusNode _userNameFocusNode = FocusNode();
      final FocusNode _pwdFocusNode = FocusNode();
    
      @override
      void initState() {
        super.initState();
    
        _pwdEditController = TextEditingController();
        _userNameEditController = TextEditingController();
    
        _pwdEditController.addListener(() => setState(() => {}));
        _userNameEditController.addListener(() => setState(() => {}));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.blue[400],
            elevation: 0,
          ),
          body: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                buildTopWidget(context),
                SizedBox(height: 80),
                buildEditWidget(context),
                buildLoginButton()
              ],
            ),
          ),
        );
      }
    
      /// 头部
      Widget buildTopWidget(BuildContext context) {
        double height = 200.0;
        double width = MediaQuery.of(context).size.width;
        return Container(
          width: width,
          height: height,
          color: Colors.blue[400],
          child: Stack(
            overflow: Overflow.visible, // 超出部分显示
            children: <Widget>[
              Positioned(
                left: (width - 90) / 2.0,
                top: height - 45,
                child: Container(
                  width: 90.0,
                  height: 90.0,
                  decoration: BoxDecoration(
                    ///阴影
                    boxShadow: [
                      BoxShadow(color: Theme.of(context).cardColor, blurRadius: 4.0)
                    ],
    
                    ///形状
                    shape: BoxShape.circle,
    
                    ///图片
                    image: DecorationImage(
                      fit: BoxFit.cover,
                      image: NetworkImage(
                          'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1569298229946&di=ea4ffb2b140ef40035772bbcee7bbdd5&imgtype=0&src=http%3A%2F%2Fcimg2.163.com%2Fcatchimg%2F20090909%2F8112139_3.jpg'),
                    ),
                  ),
                ),
              )
            ],
          ),
        );
      }
    
      /// 输入框
      Widget buildEditWidget(BuildContext context) {
        return Container(
          margin: EdgeInsets.only(left: 15, right: 15),
          child: Column(
            children: <Widget>[
              buildLoginNameTextField(),
              SizedBox(height: 20.0),
              buildPwdTextField(),
            ],
          ),
        );
      }
    
      /// 用户名
      Widget buildLoginNameTextField() {
        return Container(
          height: 40,
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.all(Radius.circular(20.0)),
          ),
          child: Stack(
            children: <Widget>[
              Positioned(
                left: 16,
                top: 11,
                width: 18,
                height: 18,
                child: Image.asset('images/login_user.png'),
              ),
              Positioned(
                left: 45,
                top: 10,
                bottom: 10,
                width: 1,
                child: Container(
                  color: Colors.black,
                ),
              ),
              Positioned(
                left: 55,
                right: 10,
                top: 10,
                height: 30,
                child: TextField(
                  controller: _userNameEditController,
                  focusNode: _userNameFocusNode,
                  decoration: InputDecoration(
                    hintText: "请输入用户名",
                    border: InputBorder.none,
                  ),
                  style: TextStyle(fontSize: 14),
                ),
              )
            ],
          ),
        );
      }
    
      /// 密码
      Widget buildPwdTextField() {
        return Container(
            height: 40,
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.all(Radius.circular(20.0)),
            ),
            child: Stack(
              children: <Widget>[
                Positioned(
                  left: 16,
                  top: 11,
                  width: 18,
                  height: 18,
                  child: Image.asset('images/login_pwd.png'),
                ),
                Positioned(
                  left: 45,
                  top: 10,
                  bottom: 10,
                  width: 1,
                  child: Container(
                    color: Colors.black,
                  ),
                ),
                Positioned(
                  left: 55,
                  right: 10,
                  top: 10,
                  height: 30,
                  child: TextField(
                    controller: _pwdEditController,
                    focusNode: _pwdFocusNode,
                    decoration: InputDecoration(
                      hintText: "请输入密码",
                      border: InputBorder.none,
                    ),
                    style: TextStyle(fontSize: 14),
                    obscureText: true,
    
                    /// 设置密码
                  ),
                )
              ],
            ));
      }
    
      /// 登录按钮
      Widget buildLoginButton() {
        return Container(
          margin: EdgeInsets.only(top: 40, left: 10, right: 10),
          padding: EdgeInsets.all(0),
          width: MediaQuery.of(context).size.width - 20,
          height: 40,
          child: RaisedButton(
            onPressed: () {
              if (checkInput()) {
                // Fluttertoast.showToast(
                //     msg: "登录成功",
                //     gravity: ToastGravity.CENTER,
                //     timeInSecForIos: 2,
                //     textColor: Colors.white,
                //     fontSize: 14.0
                // );
                // Navigator.push(
                //   context,
                //   new MaterialPageRoute(builder: (context) => new WelcomePage()),
                // );
                Navigator.pushNamed(context, "/welcome");
              }
            },
            child: Text("登录"),
            color: Colors.blue[400],
            textColor: Colors.white,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(20.0)),
            ),
          ),
        );
      }
    
      bool checkInput() {
        if (_userNameEditController.text.length == 0) {
          Fluttertoast.showToast(
              msg: "请输入用户名",
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 2,
              textColor: Colors.white,
              fontSize: 14.0);
    
          return false;
        } else if (_pwdEditController.text.length == 0) {
          Fluttertoast.showToast(
              msg: "请输入密码",
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 2,
              textColor: Colors.white,
              fontSize: 14.0);
          return false;
        }
    
        return true;
      }
    }
    

    5.主页面

    import 'package:flutter/material.dart';
    
    class WelcomePage extends StatefulWidget {
      @override
      _WelcomePageState createState() {
        return _WelcomePageState();
      }
    }
    
    class _WelcomePageState extends State<WelcomePage> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: '',
          home: Scaffold(
            appBar: AppBar(
              title: Text('data input'),
            ),
            body: Column(
              children: <Widget>[
                Image.asset('images/shiyanshi.jpg'),
                titleSelection,
                textSelection
              ],
            ),
            floatingActionButton: FancyButton(
              onPressed: () {
                _saveDataFun();
              },
            ), // This trailing comma tells the Dart formatter to use
            // a style that looks nicer for build methods.
          ),
        );
      }
    
      void _saveDataFun() {
        // Fluttertoast.showToast(
        //     msg: "保存成功!",
        //     toastLength: Toast.LENGTH_SHORT,
        //     gravity: ToastGravity.CENTER,
        //     timeInSecForIos: 1);
        // Navigator.push(
        //     context, new MaterialPageRoute(builder: (context) => new InputPage()));
        Navigator.pushNamed(context, "/input");
      }
    }
    
    Widget titleSelection = Container(
        padding: const EdgeInsets.all(32),
        child: Row(
          children: [
            /**
           * 将Column放置在Expanded中,由于Expanded会默认占据所有当前Row的空闲可用空间,所以这个Column也会自然被拉伸的占据完所有当前Row可用的空闲空间。
           */
            Expanded(
                child: Column(
                    /**
             * 将Column的crossAxisAlignment属性设置为CrossAxisAlignment.start以保证这个列中的元素(即children属性中的Widget)在水平方向上排列在当前Column的起始位置
             */
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                  Container(
                    // 第一列上方文本
                    padding: const EdgeInsets.only(bottom: 8),
                    child: Text(
                      'Amount of submited data',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  Text(
                    'for viewing only',
                    style: TextStyle(),
                  ),
                ])),
            Icon(
              Icons.star,
              color: Colors.red[500],
            ),
            Text(
              '41',
            ),
          ],
        ));
    
    Widget textSelection = Container(
      padding: const EdgeInsets.all(32),
      child: Text(
          'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
          'Alps. Situated 1,578 meters above sea level, it is one of the '
          'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
          'half-hour walk through pastures and pine forest, leads you to the '
          'lake, which warms to 20 degrees Celsius in the summer. Activities '
          'enjoyed here include rowing, and riding the summer toboggan run.',
          // 保证文字可以在单词分界的地方换行而不是在单词中间换行。
          softWrap: true),
    );
    
    class FancyButton extends StatelessWidget {
      final GestureTapCallback onPressed;
      FancyButton({@required this.onPressed});
      @override
      Widget build(BuildContext context) {
        return RawMaterialButton(
          fillColor: Colors.blue,
          splashColor: Colors.orange,
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 20.0),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: const <Widget>[
                RotatedBox(
                  //将icon进行了旋转
                  quarterTurns: 1,
                  child: Icon(
                    Icons.save,
                    color: Colors.white,
                  ),
                ),
                SizedBox(
                  width: 8.0,
                ),
                PulseAnimator(
                  //动画效果
                  child: Text(
                    'Save',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ],
            ),
          ),
          onPressed: onPressed, //点击事件
          shape: const StadiumBorder(), //添加圆角
        );
      }
    }
    
    class PulseAnimator extends StatefulWidget {
      final Widget child;
      const PulseAnimator({Key key, this.child}) : super(key: key);
      @override
      _PulseAnimatorState createState() => _PulseAnimatorState();
    }
    
    class _PulseAnimatorState extends State<PulseAnimator>
        with SingleTickerProviderStateMixin {
      AnimationController _controller;
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
            vsync: this, duration: const Duration(milliseconds: 1200))
          ..repeat();
      }
    
      @override
      Widget build(BuildContext context) {
        return FadeTransition(
          opacity: Tween(begin: 0.5, end: 1.0).animate(_controller),
          child: widget.child,
        );
      }
    }
    
    

    6.使用Navigator.push跳转页面

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

    Navigator.push(BuildContext context, Route<T> route)可以将当前页面转换成Router,压入由Navigator管理的路由堆栈(the stack of routes)中。

    用此种方式就可以实现页面之间的跳转,但是,我们会发现一个问题,单页面之间的跳转使用这种方式可能还好。如果一个页面包含多个跳转点,那么我们首先需要把相应得dart文件import,然后再重复引用上述代码,这样过于冗余。所以我们需要对路由进行统一管理。

    7.基于已命名路由调整

    除了上述跳转方式以外,Flutetr还支持将路由与页面对应起来,进行已命名的路由跳转。

    7.1更改main.dart

    路由名称通常使用路径结构:“/a/b/c”,主页默认为 “/”。

    import 'package:flutter/material.dart';
    import 'package:idata/home_page/welcome_page.dart';
    import './home_page/login_page.dart';
    import './home_page/input_page.dart';
    
    void main() {
      runApp(NamedRouter.inintApp());
    }
    /** 直接调用NamedRouter.inintApp()返回MaterialApp,代码更加简洁、清晰 */
    // class MyApp extends StatelessWidget {
    //   // This widget is the root of your application.
    //   @override
    //   Widget build(BuildContext context) {
    //     return NamedRouter.inintApp();
    //   }
    // }
    
    class NamedRouter {
      static Map<String, WidgetBuilder> routes;
      // 初始化app
      static Widget inintApp() {
        return MaterialApp(
          title: 'idata',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          // 初始路由
          initialRoute: '/',
          routes: NamedRouter.initRoutes(),
        );
      }
    
      // 初始化路由
      static initRoutes() {
        routes = {
          '/': (context) => LoginPage(),
          '/welcome': (context) => WelcomePage(),
          '/input': (context) => InputPage(),
        };
        return routes;
      }
    }
    
    

    具体应用如下

    /// 登录按钮
      Widget buildLoginButton() {
        return Container(
          margin: EdgeInsets.only(top: 40, left: 10, right: 10),
          padding: EdgeInsets.all(0),
          width: MediaQuery.of(context).size.width - 20,
          height: 40,
          child: RaisedButton(
            onPressed: () {
              if (checkInput()) {
                // Fluttertoast.showToast(
                //     msg: "登录成功",
                //     gravity: ToastGravity.CENTER,
                //     timeInSecForIos: 2,
                //     textColor: Colors.white,
                //     fontSize: 14.0
                // );
                // Navigator.push(
                //   context,
                //   new MaterialPageRoute(builder: (context) => new WelcomePage()),
                // );
                Navigator.pushNamed(context, "/welcome");
              }
            },
            child: Text("登录"),
            color: Colors.blue[400],
            textColor: Colors.white,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(20.0)),
            ),
          ),
        );
      }
    

    这样所有路由均在main.dart中定义好,其他地方只需在Navigator.pushNamed引用定义好的name便可以了。

    相关文章

      网友评论

          本文标题:Flutter(一):Navigator&Router(导航与路

          本文链接:https://www.haomeiwen.com/subject/bounyktx.html