10天学会flutter DAY8 flutter 玩转路由与导航

玩转路由与导航

    • 路由管理
      • 命名路由
      • 自定义路由切换动画
      • 注意点
        • 解决一
        • 解决二
        • 解决三
    • 完善第一个页面

路由管理

​ 路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //导航到新路由
              Navigator.push(context, MaterialPageRoute(builder: (context) {
                return SecondRoute();
              }));
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

命名路由

​ 命名路由(Named Route)即给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      //home: MainRoute(),
      //注册路由表
      routes: {
          /// '/'是特殊地址,第一个页面
        "/" :(context) => MainRoute(),
        "new_page": (context) => SecondRoute(),
      },
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.pushNamed(context, "new_page");
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

命名路由的最大优点是直观,我们可以通过语义化的字符串来管理路由。但其有一个明显的缺点:不能直接传递路由参数。假设SecondRoute,需要接受一个字符串参数tip,然后再在屏幕中心将tip的内容显示出来。因为命名路由需要提前注册到路由表中,所以就无法动态修改tip参数。

自定义路由切换动画

​ Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.push(
                context,
                PageRouteBuilder(
                  ///动画时间
                  transitionDuration: Duration(milliseconds: 500),
                  pageBuilder: (BuildContext context, Animation animation,
                      Animation secondaryAnimation) {
                    ///平移
                    return SlideTransition(
                      ///Tween:在补间动画中,定义开始点结束点
                      position: new Tween<Offset>(
                        begin: const Offset(1.0, 0.0),
                        end: const Offset(0.0, 0.0),
                      ).animate(animation),
                      child: SecondRoute(),
                    );
                  },
                ),
              );
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

同时我们也可以对动画进行组合

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: MainRoute(),
    );
  }
}

class MainRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () async {
              //导航到新路由
              var result = await Navigator.push(
                context,
                PageRouteBuilder(
                  ///动画时间
                    transitionDuration: Duration(milliseconds: 500),
                    pageBuilder: (BuildContext context, Animation animation,
                        Animation secondaryAnimation) {
                      ///透明渐变与旋转
                      return new FadeTransition(
                        opacity: animation,
                        child: new RotationTransition(
                          turns: new Tween<double>(begin: 0.5, end: 1.0)
                              .animate(animation),
                          child: SecondRoute(),
                        ),
                      );
                    },),
              );
              debugPrint("返回:$result");
            },
            child: Text("进入第二页"),
          )
        ],
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context, "结束");
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

注意点

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            RaisedButton(
              onPressed: ()  {
                  ///Navigator.push内部其实就是 Navigator.of(context).push
                  Navigator.of(context).push(MaterialPageRoute(builder: (_){
                    return new SecondRoute();
                  }));
              },
              child: Text("进入第二页"),
            )
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

这段代码运行会出现错误:10天学会flutter DAY8 flutter 玩转路由与导航_第1张图片

​ 问题关键点在于Navigator operation requested with a context that does not include a Navigator.(导航操作请求使用了不包含Navigator的上下文context)

Navigator实际上也是一个Widget,这个异常出现在Navigator.of(context)路由器的获取上,而这句代码会从当前的context的父级一层层向上去查找一个Navigator,我们当前传递的context就是MyApp,它的父级是root——UI根节点。Navigator这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得Navigator

在之前所有的路由案例中,我们的上下文是MainRoute,它的父级是MaterialApp。MaterialApp内部就会创建一个Navigator。

MaterialApp->_MaterialAppState->WidgetsApp->_WidgetsAppState

​ 所以问题就在于,Navigator需要通过MaterialApp或者它孩子的上下文。

解决一

按照此笔记最开始的正常路由演示案例来进行修改。

解决二

使用Builder

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            ///
            Builder(builder: (context){
              return RaisedButton(
                onPressed: ()  {
                  Navigator.of(context).push(MaterialPageRoute(builder: (_){
                    return new SecondRoute();
                  }));
                },
                child: Text("进入第二页"),
              );
            })
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

​ 使用Builder嵌套,Builder的参数可以看成一个回调,接收自身的context并返回布局配置。现在路由是从Builder的父亲开始查找啦,自然能找到Navigator。

解决三

使用navigatorKey

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        ///指定路由器widget的key
      navigatorKey: navigatorKey,
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("主页"),
        ),
        body: Column(
          children: <Widget>[
            Text("第一个页面"),
            RaisedButton(
              onPressed: ()  {
                ///输出Navigator
                debugPrint(navigatorKey.currentWidget.runtimeType.toString());
                navigatorKey.currentState.push(MaterialPageRoute(builder: (_){
                  return new SecondRoute();
                }));
              },
              child: Text("进入第二页"),
            )
          ],
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二页"),
      ),
      body: Column(
        children: <Widget>[
          Text("第一个页面"),
          RaisedButton(
            onPressed: () {
              //路由pop弹出
              Navigator.pop(context);
            },
            child: Text("返回"),
          )
        ],
      ),
    );
  }
}

​ 在创建Navigator的时候,会给一个key,这个key可以看成一个Widget的id。这里的**_navigator就是我们指定的navigatorKey**(如果我们没指定,会给默认值的,所以不要疑惑不指定是不是就不创建Navigator了)。而通过这个key,就能够获得这个Navigator。直接获得了路由自然不需要再去查找了!

完善第一个页面

​ 之前我们编写了第一个页面显示了banner与文章列表,一般的,在点击banner与文章条目时需要进入新界面显示文章详情,而wanandroid的文章详情是一个web页面,因此我们需要增加一个新界面用于展示web

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';

class WebViewPage extends StatefulWidget {
  final data;

  WebViewPage(this.data);

  @override
  _WebViewPageState createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  bool isLoad = true;
  FlutterWebviewPlugin flutterWebViewPlugin;

  @override
  void initState() {
    super.initState();
    flutterWebViewPlugin = new FlutterWebviewPlugin();
    flutterWebViewPlugin.onStateChanged.listen((state) {
      if (state.type == WebViewState.finishLoad) {
        // 加载完成
        setState(() {
          isLoad = false;
        });
      } else if (state.type == WebViewState.startLoad) {
        setState(() {
          isLoad = true;
        });
      }
    });
  }

  @override
  void dispose() {
    flutterWebViewPlugin.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    ///WebView插件
    return WebviewScaffold(
        appBar: AppBar(
          title: Text(widget.data['title']),
          ///appbar下边摆放一个进度条
          bottom: PreferredSize(
              preferredSize: const Size.fromHeight(1.0),
              child: const LinearProgressIndicator()),
          ///透明度
          bottomOpacity: isLoad ? 1.0 : 0.0,
        ),
        withLocalStorage: true, //缓存,数据存储
        url: widget.data['url'],
        withJavascript: true);
  }
}

flutter_webview_plugin是一个插件,需要引入:flutter_webview_plugin: ^0.3.0+2

首先我们加入banner的点击事件,在page_article.dart中修改_bannerView方法:

Widget _bannerView() {
    var list = banners.map((item) {
      ///自动增加点击水波纹的widget
      return InkWell(
        child: Image.network(item['imagePath'], fit: BoxFit.cover), //fit 图片充满容器
        ///点击事件
        onTap: () {
          ///跳转页面
          Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
            return WebViewPage(item);
          }));
        },
      );
    }).toList();
    return list.isNotEmpty
        ? BannerView(
            list,
            intervalDuration: const Duration(seconds: 3),
          )
        : null;
  }

然后还有文章条目的点击,在article_item.dart文件中修改build方法最后return的内容

 return Card(
      ///阴影效果
      elevation: 4.0,
      child: InkWell(
        child: column,
        onTap: () {
          Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
              ///因为webview获取的是map的url地址,而文章数据中文章详情的key是link
            itemData['url'] = itemData['link'];
            return WebViewPage(itemData);
          }));
        },
      ),
    );

总结

都看到这里了,能不能留个赞或者是评论呢?

你可能感兴趣的:(Flutter,flutter,android,ios)