美文网首页
Flutter学习之界面路由

Flutter学习之界面路由

作者: echoSuny | 来源:发表于2020-06-06 20:54 被阅读0次

在App当中,各个页面之间的跳转是必不可少的。比如最基本的startActivity()。当然还有Fragment之间的切换。正式由于这些最基本的功能才能展示应用当中丰富多彩的页面以及功能。那么在Flutter当中是如何跳转页面的呢?下面就一起来了解一下。

页面路由

首先定义一个主页:

void main() => runApp(HomePage());

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "第一个页面",
      home: Scaffold(
        appBar: AppBar(
          title: Text("首页"),
        ),
        body: Center(
          child: RaisedButton( 
            child: Text("跳转"),
            onPressed: () { /// 点击事件
     
            },
          ),
        ),
      ),
    );
  }
}

界面十分简单,在屏幕的中央有一个按钮,并设置了点击事件,只不过点击时间内部没有任何逻辑:



下面来编写第二个页面:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("第二个页面");
  }
}

界面也十分简单,只是显示了一个文本。下面在第一个页面的点击事件内部加上跳转:

onPressed: () {
   Navigator.push(context, MaterialPageRoute(builder: (_) {
      return SecondPage();
   }));
  },

在Flutter中使用的是Navigator类来进行页面之间的跳转。下面就来点击一下看看能否完成页面之间的跳转:



可惜的是没有跳转过去,同时我们可以看到在控制台输出了一段错误日志:



第三行的大概意思是导航器需要一个不包含导航器的上下文来操作。那么点开最上面的一行报错信息进入源码当中来看一下:

直接跳转到了Navigator类的1475行。咦?似曾相识的感觉。这不就是错误日志显示的内容吗?确实,日志中显示的内容就是由这段异常抛出的。上面的关键字assert表示这是一个断言,要求navigator不为空并且nullOk为false,断言就可以成功。可以看到在1468行处nullOK为false,也没有对它修改的地方。那么真正导致断言失败的就是navigator为null了。而navigator则是由上面的三目表达式返回的。rootNavigator是一个布尔值在1467行处,默认为false。那么navigator就是由后面的ancestorStateOfType()方法返回的。那么就需要来看一下为何这个方法没有成功的返回navigator:



这是一个抽象方法,属于BuildContext类,没有任何的逻辑,那我们需要去它的实现类中来看一下:
实现类

也就是说我们在上面使用Navigator.push()进行跳转的时候传入的context对象其实就是一个Element。也就是3469行处的_parent。换句话说我们在调用Navigator.push()时候传入的context其实就是HomePage自己。知道了这些那么现在回过来分析下面的代码。首先判断ancestor是否为null,很显然我们的HomePage不为null,那么就会开始循环。而HomePage是一个StateLessElement,那么久不会进入这个if,那么就会把ancestor赋值为它的父亲,也就是HomePage的父亲。总结下来就是会一直往上找,类似于Android中会一直找到root。找到最上层的时候发现_parent为null,也就跳出了循环,最后statefulAncestor?.state就返回了null。
那我们让HomePage继承StateFullWidget是不是就可以了呢?其实也是不可以的。Navigator其实也是一个Widget,归根结底就是root不会创建Navigator。
其实我们使用的MaterialApp当中就会替我们创建Navigator。

MaterialApp是一个StateFullWidget,那么就需要去看它的createState()方法:



这是一个state,那么就要看它的build()方法:

build()方法又返回了一个WidgetsApp,继续追踪:

还是一样的套路,最终需要去看WidgetsApp中state的build()方法:

终于看到navigator的创建了。
下面我们就只需要想办法用MaterialApp的上下文就可以了:
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "第一个页面",
        // 嵌套一层Builder
        home: Builder(builder: (context) {
          return Scaffold(
            appBar: AppBar(
              title: Text("首页"),
            ),
            body: Center(
              child: RaisedButton(
                child: Text("跳转"),
                onPressed: () {
                  Navigator.push(context, MaterialPageRoute(builder: (_) {
                    return SecondPage();
                  }));
                },
              ),
            ),
          );
        }));
  }
}

可以看到跳转成功了。其实Builder也是一个Widget,需要我们传入一个匿名方法。如果这样看着嵌套比较难受的话,我们可以对其进行分离:

class HomePage extends StatelessWidget {
  // 对builder进行抽离
  Widget mBuilder(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("跳转"),
          onPressed: () {
            Navigator.push(context, MaterialPageRoute(builder: (_) {
              return SecondPage();
            }));
          },
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "第一个页面", home: Builder(builder: mBuilder));
  }
}

添加完了之后我们的页面层是像上图那样的。其实不一定非要放到MatrerialApp的下一层,只需要保证BuildContext的获取在MaterialApp下就可以,放在Scaffold或者Center下也都是可以的。
其实不使用Builder也可以完成跳转,我们只需要再创建一个类似于HomePage的子view的Widget也可以实现:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "第一个页面", home: HomePageContent());
  }
}
// 真正展示UI内容的空间
class HomePageContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("跳转"),
          onPressed: () {
            Navigator.push(context, MaterialPageRoute(builder: (_) {
              return SecondPage();
            }));
          },
        ),
      ),
    );
  }
}

其实这和使用Builder的道理是一样的,也是创建一个Widget放在了MaterialApp下,不过这样看起来会更清晰一些。
除了上述的两种方式,我们还可以使用命名路由的方式来进行跳转:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "第一个页面",
        routes: {
          "/": (_) => HomePageContent(),
          "/second_page": (_) => SecondPage()
        },
//        home: HomePageContent() 需要注释掉这一行
    );
  }
}

class HomePageContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("跳转"),
          onPressed: () {
            // 使用pushNamed方式跳转,传入上方路由表中定义的key
            Navigator.pushNamed(context, "/second_page");
          },
        ),
      ),
    );
  }
}

可以看到在MaterialApp下使用routes属性,然后传入一个key为String,value为匿名方法的Map集合。需要注意的是 “/”是一个默认的路由地址。如果路由表中存在了 “/”,那么home属性就需要去掉,否则就会报错。
我们的SecondPage现在是一个黑色的界面,我们现在要对它进行改变:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("第二个界面")),
      body: Text("第二个界面"),
    );
  }
}

可以看到Flutter自动为我们加上了返回按钮,这都是MaterialApp为我们做的。因为我们的SecondPage也是在MaterialApp下的。

页面通信

我们在Android中经常需要在界面中使用intent和startActivityForResult()来进行页面之间的通信。对应的Flutter也有这样的方式。
首先我们定义一个bean:

class User{
  String name;
  int age;

  User(this.name, this.age);

  @override
  String toString() {
    return 'User{name: $name, age: $age}';
  }
}

下面就需要在跳转的时候携带上数据就可以了:

Navigator.pushNamed(context, "/second_page",arguments: User("echo", 18));

最后我们在SecondPage处接收:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// 接收参数
    var data = ModalRoute.of(context).settings.arguments;
    print("-----$data");
    return Scaffold(
      appBar: AppBar(title: Text("第二个界面")),
      body: Text(data.toString()),
    );
  }
}

可以看到成功的完成了数据的传递。那要怎么往回传递呢?首先我们要把SecondPage改造一下,需要添加一个按钮,点击按钮的时候跳转回第一个页面。虽然AppBar的返回箭头也可以返回,但是那样不能够传递参数:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var data = ModalRoute.of(context).settings.arguments;
    print("----->$data");
    return Scaffold(
      appBar: AppBar(title: Text("第二个界面")),
      body: Center(
        child: RaisedButton(
          child: Text("返回HomePage"),
          onPressed: () {
            // 使用pop来进行弹栈携带数据返回
            Navigator.pop(context, "hello echo");
          },
        ),
      ),
    );
  }
}

下面需要在HomePage处接收:

onPressed: () async {
            // 使用pushNamed方式跳转,传入上方路由表中定义的key
            // 使用async-await获取数据
            var backData = await Navigator.pushNamed(context, "/second_page",
                arguments: User("echo", 18));
            print("-----$backData");
          },
跳转动画

不知道有没有注意到使用Material风格的路由跳转的的时候,打开页面的时候是一个从下往上并且带有透明度的动画效果,退出的时候反之。我们都知道在Android中我们是可以设置activity之间的转场动画的。那Flutter也同样支持。
首先需要对跳转的Navigator进行修改:

Navigator.push(context, PageRouteBuilder(pageBuilder:
                (BuildContext context, Animation<double> animation,
                    Animation<double> secondaryAnimation) {
              return SecondPage();
            }));

可以看到没有任何效果就直接跳转了,很生硬。那么在Flutter中提供了对应的动画,例如把retrun SecondPage()改为如下代码:

return SlideTransition(
                position: animation.drive(
                    Tween(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))),
                child: SecondPage(),
              );

意思就是从x坐标为1,y为0滑动到x为0,y为0处。也就是从右往左滑动进入:



加上透明度:

return FadeTransition(opacity: animation,child: SlideTransition(
                position: animation.drive(
                    Tween(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))),
                child: SecondPage(),
              ),);
            }));

相关文章

网友评论

      本文标题:Flutter学习之界面路由

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