flutter手势(事件监听) 和 事件总线

这个转自我自己的有道云 想看图片去那里
文档:Day3_25 手势(事件监听), 事件总线 和 …
链接:http://note.youdao.com/noteshare?id=67936e30b5c44258a7394387afb36209&sub=878F6BFA1DD046E9909C088B7CBFA512

手势(事件监听) 和 事件总线

路由和导航

首先我们先解决一个问题

问题解决

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsNC7jRC-1586237096386)(DAF4C87A98FD407F928DFDAB55045CB8)]

如果使用较小的屏幕会出现超出安全区的情况

我们来分析 不要怕

这里是因为这个这个Start组件 他是一个Row

但是这个里面的宽度 大于他本身的宽度

  1. 所以我们一个是可以改小他的size

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vWp2fB0-1586237096388)(1491748893F64B538432D3CD808BD9E4)]

这个地方我们希望它能自己变小 所以我们这里可以使用这个Expanded

这个Expanded的作用是 占据剩下的空间

我们在外面包裹一层Expanded

也不行因为他是里面也是一个一个的小组件

  1. 使用FittedBox
  Widget buildContentInfoRate() {
    return FittedBox(
      child: Row(
        children: <Widget>[
          HYStarRating(rating: movie.rating, size: 20),
          SizedBox(width: 6),
          Text("${movie.rating}", style: TextStyle(fontSize: 16))
        ],
      ),
    );
  }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9aWWkYI9-1586237096389)(27F895B7DCE24249874B3B7B9EFE9E1C)]

它就可以 自动的适配

如果里面的组件小于外面的组件

我们就会缩小 里面的组件然后 放下来

这样没有报错了

但是这个也不是最好的解决方案

我们希望 能够自动的适配设备

我们可以自己定义rpx这个单位

也可以使用 第三方官方库

这个专门开课来做

一. 事件监听

事件就是点击长按 滑动

这些都会在手机当中形成事件

我们这里就是要让程序对这些事件有响应

flutter中手势分两种

  • 第一原始指针事件
    • 这个东西就是最基础的点击 指针的位置和移动
  • 第二手势识别
    • 这个东西就是对原始事件的封装
    • 只不过这个东西是框架帮忙封装的
2.1 原始指针事件

但是官方其实更建议我们使用手势

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrrjayOP-1586237096390)(1018BB5923F549049C995467C211548C)]

再开中我们更建议使用手势而不是原始指针事件

Pointer事件 代表的是人机交互的原始数据 一共有四种事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnEsST08-1586237096390)(88E9A24A7A08410C872655DD0ABA234D)]

Pointer的原理是什么呢

  • 指针的落下的时候, 框架做了一个hit test的操作
  • 事件会沿着最内部的组件向根冒泡分发
  • 并不存在用于取消或者停止指针事件进一步分发的机制

而且注意我们是没有办法直接停掉这个 冒泡的过程的

那我们到底怎么来做这个

我们到底怎么来做这个监听的呢

我们还是建立最基础的东西

根据它事件的名称 很明显 这个地方

  • up 监听它抬起
  • down 监听它按下
  • move 监听它移动
  • Cancel 它取消的动作有些时候这个手势可能会被一些东西打断这个时候我们就可以用这个东西

比如说我们手指按下的同时我们的电话来了 这个时候我们就触发这个事件

或者说我们的闹钟响了

这个时候 我们可以嵌套一个Listener然后来监听它的 事件

它这里有一个onPointerDown: 这个就是点击触发的回调函数

它有一个参数

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Listener(
          onPointerDown: (event) {
            print(event.position);
          },
          child: Container(
            color: Colors.red,
            width: 200.px,
            height: 200.px
          ),
        ),
      ),
    );
  }
}

同样我们也可以触发它的其他的方法

import "package:flutter/material.dart";
import 'package:learn_flutter02/extension/size_fit.dart';

import "../extension/int_extension.dart";

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    HYSizeFit.initialize();
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Listener(
          onPointerDown: (event) {
            print("手指按下 ${event}");
          },
          onPointerMove: (event) {
            print("手指移动 $event");
          },
          onPointerSignal: (event) {
            print("sinal $event");
          },
          onPointerCancel: (event) {
            print("Cancel $event");
          },
          onPointerUp: (event) {
            print("手指抬起: $event");
          },
          child: Container(
            color: Colors.red,
            width: 200.px,
            height: 200.px
          ),
        ),
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ob6OHY8g-1586237096391)(0DE82B3AAF504424AC76C91A05E69FCA)]

但是实际上这个event他经常用的是position 位置之类的信息

这里我们可以拿到他的position

          onPointerDown: (event) {
            print("手指按下 ${event.position}");
          },
          
          

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AK6dImUE-1586237096391)(4D740E2352374A378A713F58FFE75795)]

但是这个是相对于屏幕的位置

我们也可以获得相对他的子Widget的位置

          onPointerDown: (event) {
            print("手指按下 ${event.position}");
            print("手指按下 ${event.localPosition}");
          },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F0Db0ByX-1586237096392)(EF10002EED8646F0AECB1860B7795D40)]

现在这样就可以了

这个0, 0还是不容易点到的

这个就是我们的原始指针事件

但是它的这个是事件比较少的

比如我们想用长按 我们就不是很好做了

我们可以改使用手势

这个也是官方建议的

2.2 手势识别的

Gesture

手势分成非常度多的种类

Gesture分层非常多的种类:

点击:

  • onTapDown:用户发生手指按下的操作
  • onTapUp:用户发生手指抬起的操作
  • onTap:用户点击事件完成
  • onTapCancel:事件按下过程中被取消

双击:

  • onDoubleTap:快速点击了两次

长按:

  • onLongPress:在屏幕上保持了一段时间

纵向拖拽:

  • onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动;
  • onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动;
  • onVerticalDragEnd:指针和屏幕产生接触结束;

横线拖拽:

  • onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动;
  • onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动;
  • onHorizontalDragEnd:指针和屏幕产生接触结束;

移动:

  • onPanStart:指针和屏幕产生接触并可能开始横向移动或者纵向移动。如果设置了 onHorizontalDragStart 或者 onVerticalDragStart,该回调方法会引发崩溃;
  • onPanUpdate:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,该回调方法会引发崩溃。
  • onPanEnd:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了 onHorizontalDragEnd 或者 onVerticalDragEnd,该回调方法会引发崩溃。

手势它的动作就非常的多

我们这里列的就相对比较少 在源码里面差不多有50 个

如何设置手势

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: GestureDetector(
          child: Container(
            width: 200,
            height: 200,
            color: Colors.pink
          ),
        )
      ),
    );
  }
}

我们发现这个up 和 down 是有参数的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pWmT4wt-1586237096393)(503644AD56334BC995539247770D06E2)]

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            print("按下");
          },
          onTapDown: (event) {
            print("手势按下");
          },
          onTapUp: (event) {
            print("手势抬起");
          },
          onTapCancel: () {
            print("手势取消");
          },
          child: Container(
            width: 200,
            height: 200,
            color: Colors.pink
          ),
        )
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyg4BFP2-1586237096393)(001B15BFCEEB4C79BCC6FC16E822B46E)]

但是如果我们想要拿到它的位置的话 我们可以

它这里就科学的多了

          onTapDown: (detail) {
            print("手势按下");
            print(detail.globalPosition);
            print(detail.localPosition);
          },
          onTapUp: (detail) {
            print("手势抬起");
            print(detail.globalPosition);
            print(detail.localPosition);
          },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-71X9PKy0-1586237096394)(6AF058AEE5584386BF595EA6F6F0F239)]

          onDoubleTap: () {
            print("手指双击");
          },

这里和一些平台一样我们做长按的时候 它会取消掉 按下和抬起

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVuKFdl2-1586237096394)(015F7C57FCC34CB6956E0E8FB7AB5B94)]

但是长按没有

它这里是

          onLongPress: () {
            print("手指长按");
          },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGvFdzZU-1586237096394)(CC3E7AA5A68E44C39D12043417919FDF)]

import 'dart:async';

import "package:flutter/material.dart";
import 'package:learn_flutter02/extension/size_fit.dart';

import "../extension/int_extension.dart";

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    HYSizeFit.initialize();
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {
  int time = 0;
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            print("按下");
          },
          onTapDown: (detail) {
            flag = true;
            time = 0;
            countTime();
            print("手势按下");
            print(detail.globalPosition);
            print(detail.localPosition);
          },
          onTapUp: (detail) {
            print("手势抬起");
            print(detail.globalPosition);
            print(detail.localPosition);
          },
          onTapCancel: () {
            print("手势取消");
          },
          onDoubleTap: () {
            print("手指双击");
          },
          onLongPress: () {
            flag = false;
            print("手指长按");
          },
          child: Container(
            width: 200,
            height: 200,
            color: Colors.pink
          ),
        )
      ),
    );
  }

  void countTime() {
    time++;
    if( flag && time < 500 ) {
      Timer(Duration(milliseconds: 1), () {
        print(time);
        countTime();
      });
    }
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7N0w0Rl-1586237096395)(C382183C0BF84AB690F92B50F00C2AFE)]

这个长按的事件是100ms左右

手势冒泡

我们的这个冒泡的阻止 是通过一种间接的方式来实现的

我们现在做一个东西 希望Container 嵌套 Container显示

先做一个Container

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.yellow
        )
      ),
    );
  }
}

然后我们希望在里面又嵌套一个Container

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.yellow,
          child: Container(
            width: 100,
            height: 100, 
            color: Colors.red
          ),
        )
      ),
    );
  }
}

试一下呢

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2I7Vysi-1586237096396)(7977D9FF12024DD5AEBDB10515FF3BCD)]

结果变成全部红色的了

回顾一下我们之前讲 Container的时候 它会使它的子组件填满它本身

我们在Container包裹了一个Container的时候它会扩展到外面的Container

如果我们想要做嵌套的话我们可以给他嵌套一个Column

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.yellow,
          child: Column(
            children: <Widget>[
              Container(
                width: 100,
                height: 100,
                color: Colors.red
              ),
            ],
          ),
        )
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZ6QRcRn-1586237096396)(6B4CC3902B714309AA51E26B4852205E)]

还有一个办法就是设置一个alignment也是可以的

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.yellow,
          alignment: Alignment.center,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.red
          ),
        )
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcuqgpho-1586237096397)(0172514FA6CD41478D8E044CE0804D30)]

因为Container的源码里面如果你传了alignment的话它就会创建一个Align

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cgblvDW8-1586237096398)(B5C12063E2104ACE9D2A7CE15E328456)]

如果这样的话我们就相当于包裹了一个Align

开发里面如果要Container做嵌套的话我们就可以这样

不用多嵌套了

我们现在希望给这两个Container做一个手势

但是我们希望我们里面Gesture不要传到外面

所以之类我们就像整一个冒泡的阻止

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: GestureDetector(
          onTapDown: (detail) {
            print("outter click");
          },
          child: Container(
            width: 200,
            height: 200,
            color: Colors.yellow,
            alignment: Alignment.center,
            child: GestureDetector(
              onTapDown: (detail) {
                print("inner click");
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red
              ),
            ),
          ),
        )
      ),
    );
  }
}

我们这样做一个东西

我们发现它这里有点怪就是这个内部的点击事件

偶尔会传到外面去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLkgE09A-1586237096399)(78344741810C46AB96670C3AC5DA8E37)]

所以它偶尔会传到外面去

所以我们希望彻底阻绝他们

网上的话有些资料是说我们可以设置一些参数来阻止这个东西

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: GestureDetector(
          onTapDown: (detail) {
            print("outter click");
          },
          child: Container(
            width: 200,
            height: 200,
            color: Colors.yellow,
            alignment: Alignment.center,
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTapDown: (detail) {
                print("inner click");
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red
              ),
            ),
          ),
        )
      ),
    );
  }
}

但是实际使用之后发现它还是不行

Android好像就可以了

所以有些时候不用太信奉网上的东西

包括官方文档都是有可能错的 像是中文文档它翻译的时候经常就出问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f4i81jgz-1586237096399)(AC770A69953749C7914DBBB5D60D14EE)]

针对于这种情况

stackfloor上面有一个回答 获得了很多的星

这两个东西就不要有嵌套关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtma6xP5-1586237096400)(198BF5DDE2E24389AF8CB6051B28B591)]

那我们有想做这样的界面但是它又要有嵌套关系

我们可以使用Stack

这里可以在最外层包裹(这里可以选择包裹一个Column然后再把它改成 Stack 因为Stack是没有快捷生成的 而Column生成出来子元素又是 children)

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            GestureDetector(
              onTapDown: (detail) {
                print("outter click");
              },
              child: Container(
                width: 200,
                height: 200,
                color: Colors.yellow,
                alignment: Alignment.center,
              ),
            ),
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTapDown: (detail) {
                print("inner click");
              },
              child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.red
              ),
            ),
          ],
        )
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hcCu0Db-1586237096400)(183B16EF19B94726898E0E7A2B45DB24)]

这样就不会出现问题了

这个地方外面肯定是不会响应的

那么我们又提一个需求 我们现在只希望 它响应外面的点击事件

我们这里可以使用 IgnorePointer 来实现阻止内层冒泡

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            GestureDetector(
              onTapDown: (detail) {
                print("outter click");
              },
              child: Container(
                width: 200,
                height: 200,
                color: Colors.yellow,
                alignment: Alignment.center,
              ),
            ),
            IgnorePointer(
              child: GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTapDown: (detail) {
                  print("inner click");
                },
                child: Container(
                    width: 100,
                    height: 100,
                    color: Colors.red
                ),
              ),
            ),
          ],
        )
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWjGxMct-1586237096401)(2C4609DFEBB74DE6B6BA92713DFC4DB4)]

这样就可以了 相当于它用了一种的另类的方法来实现阻止冒泡

事件总线(跨组件事件的传递)

比如说我们现在有两个widget

我们想要在两个隔的很远的widget进行通讯

有可能同页但是隔了很远 还有可能不同页

但是我们还是希望能做出响应

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPafFkCB-1586237096401)(7C8EE88693174398A190E5A2B95A1F6E)]

如果这样的话我们可能就需要被响应位置的传对应的回调函数 隔很多层将这个回调函数传过来

  • 如果不想这样做的话我们怎么做呢

这个时候我们就可以使用eventbus事件总线

之前我们没有这个东西 所以自己要搞的话就要需要一个Map<String, List>

如果你要监听对应的String的事件 你就可以把这个回调函数加到哪里

一旦我们发出事件的时候我们就把对应的事件取出来

然后我们就会调用对应的Callback 但是它这里需要传一个泛型 如果不传你就不知道它里面的返回的东西是什么

这里有人已经写好了一个事件总线我们直接用就是了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kEFNRspg-1586237096402)(C6CB6C3DBEBE40F488B8FE4A20558B3E)]

这个东西就叫event_bus

它这个东西是基于dart Streams来实现的

dart里面有很多的语法的 我们学习了基础的语法以后 我们掌握这些语法其实还比较容易的

我们现在有这样的一个需求

我们建立这样的一个widget 我们希望能点击button以后不在同一个层级的Text能发生改变

就希望这里可以发生一个通讯 你学会 了这个东西我们其他的地方也是一样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7NbNT4n-1586237096402)(108FFDDBFD46473DA1AE7E40F1CB78FC)]

我们建立下面的Text最好选择fulWidget因为我们需要改变里面的值所以最好选择可以记录状态的东西

import "package:flutter/material.dart";

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            HYButton(),
            HYText()
          ],
        ),
      ),
    );
  }
}

class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(Icons.add_a_photo),
      onPressed: () {
        
      },
    );
  }
}

class HYText extends StatefulWidget {
  @override
  _HYTextState createState() => _HYTextState();
}

class _HYTextState extends State<HYText> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("文本"),
    );
  }
}


我们的Text 和 button互为兄弟组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJtrHhe0-1586237096403)(1A58586706F8406D9A8466664E6539B7)]

所以如果是现在这我们可能需要拿到 Text 然后再拿到里面的回调函数

让后让这个Text做一个改变 这里我们可能就需要globalKey 了

import "package:flutter/material.dart";

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {
  final GlobalKey<_HYTextState> homeKey = GlobalKey<_HYTextState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            HYButton(homeKey),
            HYText(key: homeKey)
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.change_history),
        onPressed: () {
          homeKey.currentState.setString();
        },
      ),
    );
  }
}

class HYButton extends StatelessWidget {
  final GlobalKey<_HYTextState> tempKey;

  HYButton(this.tempKey);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(Icons.add_a_photo),
      onPressed: () {
        tempKey.currentState.setString();
      },
    );
  }
}

class HYText extends StatefulWidget {
  HYText({Key key}):super(key: key);

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

class _HYTextState extends State<HYText> {
  String _text = "文本";

  void setString() {
    setState(() {
      _text = "改版的文本";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(_text),
    );
  }
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AmrdEr1c-1586237096403)(4E4FE2C7967347FA8F921A4BEC79CE9A)]

这样就可以了

这里还是可以这样做 但是如果你层级太多你就不好做了

所以我们这里就可以使用时间总线

首先它是一个第三方的东西

这个东西的用法是分成三步的

  1. 创建全局的event_bus对象

在全局的地方创建这个event_bus

import 'package:event_bus/event_bus.dart';
import "package:flutter/material.dart";

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

final eventBus = EventBus();
  1. 在对应的位置发出事件啊
class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(Icons.add_a_photo),
      onPressed: () {
        eventBus.fire("你好啊 李银河");
      },
    );
  }
}
  1. 监听发出的事件

它这里是靠事件的类型来监听的 所以 我们在监听的时候 一般不会使用这个String来作为发出或者监听的类型

就算是要发出String也是会包装一个对象

这个每次发出事件的时候 都最好定义一个模型 官方说是定义一个event

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hIghG46-1586237096404)(4149FC2C6E4746CAAFEC43A5BA9656F8)]

class HYText extends StatefulWidget {

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

class _HYTextState extends State<HYText> {
  String _message = "hello world";

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

//    这里我们一般开发的时候我们不会监听这个String类型的东西
    eventBus.on<String>().listen((data) {
      print(data);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(_message),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oASuB5np-1586237096404)(D135D0C39F404C6FB4C5F2951D4C87A3)]

这样就可以了

一般我们会把它整成一个类 一个model 这个model里面就给他保存一些数据

然后发出这个对象的

import 'package:event_bus/event_bus.dart';
import "package:flutter/material.dart";

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

final eventBus = EventBus();

class UserInfo {
  String nickName;
  int age;

  UserInfo(this.nickName, this.age);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            HYButton(),
            HYText()
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.change_history),
        onPressed: () {
        },
      ),
    );
  }
}

class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(Icons.add_a_photo),
      onPressed: () {
        UserInfo user = UserInfo("nick", 23);
        eventBus.fire(user);
      },
    );
  }
}

class HYText extends StatefulWidget {

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

class _HYTextState extends State<HYText> {
  String _message = "hello world";

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

//    这里我们一般开发的时候我们不会监听这个String类型的东西
    eventBus.on<UserInfo>().listen((data) {
      print("name: ${data.nickName} age: ${data.age}");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(_message),
    );
  }
}



[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GmLW9cQ-1586237096405)(00B353112CC14ABDADB75D8F990003F7)]

import 'package:event_bus/event_bus.dart';
import "package:flutter/material.dart";

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

final eventBus = EventBus();

class UserInfo {
  String nickName;
  int age;

  UserInfo(this.nickName, this.age);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            HYButton(),
            HYText()
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.change_history),
        onPressed: () {
        },
      ),
    );
  }
}

class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(Icons.add_a_photo),
      onPressed: () {
        UserInfo user = UserInfo("nick", 23);
        eventBus.fire(user);
      },
    );
  }
}

class HYText extends StatefulWidget {

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

class _HYTextState extends State<HYText> {
  String _message = "hello world";

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

//    这里我们一般开发的时候我们不会监听这个String类型的东西
    eventBus.on<UserInfo>().listen((data) {
      print("name: ${data.nickName} age: ${data.age}");
      setState(() {
        _message = "name: ${data.nickName} age:  ${data.age}";
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(_message),
    );
  }
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mt7cA8xt-1586237096406)(B98B500C3F59494EBFEB612BEE53C245)]

这样就可以了

开发当中我们用这个东西就会更方便

  • 所以我们每次用的时候就需要给他定义一个event模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vvOD9i9W-1586237096407)(567C1241E2104C82814499466A655213)]

  • 还有一个问题后面我们分文件的时候我们需要分开 这个东西怎么传

我们可以用文件专门用event_bus

这样我们就可以在不同的文件里面使用这个这个事件总线了

event_bus.dart

import 'package:event_bus/event_bus.dart';

final eventBus = EventBus();
import "package:flutter/material.dart";

import "event_bus.dart";

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

这个就是一个模块的导入

这个event_bus它是一个全局的对象 一般是不会销毁它

路由和导航

之前我们用indexStacked实现类似的功能但是

我们如果想要在这个上面添加详情页的功能的话 就要使用到路由了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGFxS89z-1586237096408)(5A7E6F641D354C6F9E32F12783B17BA9)]

跳转到右下的详情页 你就要通过类似vue之类东西的push的方式

如果你要这样的话

2.1. 认识Flutter路由

路由的概念由来已久,包括网络路由、后端路由,到现在广为流行的前端路由。

  • 无论路由的概念如何应用,它的核心是一个路由映射表
  • 比如:名字 detail 映射到 DetailPage 页面等
  • 有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发(在前端表现出来的就是页面跳转)

在Flutter中,路由管理主要有两个类:Route和Navigator

这个路由有很多 前端路由 后端路由

这种单页面复应用, spa页面它们都是用的单页面复应用 都是用的前端路由

为什么它叫前端路由可以去看我的vue的路由的部分

我们的这个东西 它其实就是像一个map的东西 一个detail就对应一个页面

这个东西就是一个路由表一样的东西

和路由器里面的东西是一样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afCDVhRE-1586237096409)(6C73A5A0FE1245E0B4715D911D375614)]

这个就是一个 路由表

在flutter如果你像管理这个路由的话 我们主要是用到两个类

Route 和 Navigator

一个页面要想被路由我们就要包装到一个路由里面

到时候我们的Navigation才能帮助我们管理 页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGfk2LdX-1586237096409)(CCF9A8EC18DC4A518A797D22A820FA8C)]

如果我们有很多的路由我们就可以把它放到一个表里面来管理

表就是路由表

2.2. Route

Route:一个页面要想被路由统一管理,必须包装为一个Route

  • 官方的说法很清晰:An abstraction for an entry managed by a Navigator.

你是一个抽象的路由它是一个入口被Navigation来管理

但是Route是一个抽象类,所以它是不能实例化的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gXKnNb59-1586237096410)(6FAB26E02BC04BCBA852AE20558A4B81)]

我们来看一下这个它有很多的子类

但是我们常用的一般就会直接写在这里顶上面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnpeKx4f-1586237096411)(6781EDA013254911A57720E0FCCF09E6)]

它希望我们用这个MaterialPageRoute来替代 屏幕的入口

我们就可以对这个东西来做一个实例化

/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.

MaterialPageRoute就是很多 Route功能的综合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwHAcaGs-1586237096411)(1B41F5FCD9004F92A16C37CC0CCCB7FB)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8vTkANO-1586237096412)(46F993F09F584443B571D55FFBA585FB)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-76a4ywdE-1586237096412)(CA77178E6CB8445CA96E1600A4D5FC4F)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcWykF2B-1586237096413)(E49D0A8CF59641EFBA6BF99792BBE938)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcASRpgf-1586237096414)(79D7F99309464F6CB3810E1DA476BBB5)]

也就是说它有这些东西的功能

所以以后我们在做跳转的时候我们就会给他包裹一个MaterialPageRoute用的是最多的

  • 表现

Android 是从底部画出来的

关闭是从上面到限免

IOS是从做左到右

当然我们也有办法在Android上用IOS的切换模式

用它也是可以的

2.3 Navigator

Navigator: 管理里面的Route的Widget, 通过一个Stack来进行管理的

  • 官方的说法也很清晰:A widget that manages a set of child widgets with a stack discipline.

也就是说先进后出

  • 但是我们是不用直接创建Navigator 因为这个对象它MaterialApp、CupertinoApp、WidgetsApp它们默认是有插入Navigator的
  • 但是我们是不用去直接创建的

这个对象是通过InheritedWidget的方式来管理的

我们可以看到里面有一个of方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bltDxGQo-1586237096415)(E283E3C642BF4DEAAC2D3AEFD35D7789)]

可以看到它先是拿到一个 rootNavigator 然后看里面有没有值

如果没有值就去里面查找 虽然这个和之前的那个名字不一样

但是本质上都是沿着Element树 去查找对应的Navigator

不过它这里是用的State的方式来查找的

其实这里就因该是找到自动创建的那个Navigator对象

同样需要传递一个context 拿到这个Navigator我们就可以跳转到对应的页面了

    Navigator.of(context).pushNamed("");
  • Navigator几个最常用的方法
  1. push: 这个方法是让我们传入一个Route
  2. pushNamed: 是传入的是要给名字
  3. pop:这个就是弹出栈
  • 使用

我们创建一个跳转的页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rA04fQ3c-1586237096416)(662E1A71B2EB4185B0A0D2A0C41AD486)]

import "package:flutter/material.dart";

class HYDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("detail")
      ),
      body: Center(
        child: Text("测试"),
      ),
    );
  }
}

然后回来

这里我们使用

跳转都可以 那个省略了of就是在这个函数里面把它放到一起了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UE69ka8N-1586237096417)(09E2A67A5292482CBC2B26408DB48D73)]

    Navigator.of(context).push(route);
    Navigator.push(context, route)

但是我们跳转的时候 不能直接用HYDetailScreen这样的形式进行跳转

我们需要把它封装到一个route里面

我们就需要创建一个route

import "package:flutter/material.dart";
import 'package:learn_flutter02/day11_route_Protice/detail.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("跳转到详情页面"),
          onPressed: () => _jumpToDetail(context),
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen();
        }
      )
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHzOtl6w-1586237096417)(46161F72A5094905A2423FA9588964B9)]

我们现在是点击上面的那个东西返回

那我们现在希望点击一个按钮然后返回

import "package:flutter/material.dart";

class HYDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("detail")
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text("测试"),
            RaisedButton(
              child: Text("回到首页"),
              onPressed: () => _backToHome(context),
            )
          ],
        ),
      ),
    );
  }

  void _backToHome(BuildContext context) {
    print("回到首页");
    Navigator.of(context).pop();
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-th34Gj3K-1586237096418)(6175A986D4F54986B40BAC96BF568C8B)]

另外的跳转方式

我们这里的跳转时方式是 用了一个MaterialPageRoute然后来做的一个跳转

  • 我们还希望能获得参数

这样的话我们如果可以获得一个商品的id那我们就可以对应的请求数据

import "package:flutter/material.dart";
import 'package:learn_flutter02/day11_route_Protice/detail.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      home: HYHomePage(),
    );
  }
}

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("跳转到详情页面"),
          onPressed: () => _jumpToDetail(context),
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
  }
}
import "package:flutter/material.dart";

class HYDetailScreen extends StatelessWidget {
  final String _message;

  HYDetailScreen(this._message);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("detail")
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(_message, style: TextStyle(fontSize: 30)),
            RaisedButton(
              child: Text("回到首页"),
              onPressed: () => _backToHome(context),
            )
          ],
        ),
      ),
    );
  }

  void _backToHome(BuildContext context) {
    print("回到首页");
    Navigator.of(context).pop();
  }
}

这种一般的路由我们可以通过构造器来传递参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJV0KYOL-1586237096418)(E5B9EB01DFB840719C3895F2C00211FF)]

我们返回的时候 也有可能会想要传递对应的参数

我们的pop也是有一个可选参数的

detail.dart

  void _backToHome(BuildContext context) {
    print("回到首页");
    Navigator.of(context).pop(" a detial message");
  }

但是有个问题就是我们返回了 你怎么拿到这个东西

这个东西多半只能用回调来拿

我们发现这个东西它返回的是一个Future

   void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
    });
  }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cko9oU3-1586237096419)(670E9980F948466DBA77C25EFDD61F03)]

它这里返回了一个Future 所以

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UqhIHlxP-1586237096419)(3A943F984C2A4976A27AFFF1FDC91FFA)]

main.dart

class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {
  String message  = "home message";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail(context),
            ),
            Text(message)
          ],
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
      message = result;
    });
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbeVurcR-1586237096420)(4234E853CC8C4A548777218ED032ED3E)]

但是你有没有考虑过我们使用它默认的坐上的按钮也要返回数据

如果不这样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DL1n5t8p-1586237096420)(9FD8D0D02D824CCEBA557BCF4EAC17DE)]

就会报错因为这里我们把一个null赋值给了一个字符串变量

所以这里我们返回的时候就有问题了

所以这里我们希望我们再点返回按钮的时候它也要携带一些返回的信息

这个东西我们用两个东西来做 默认情况下 它是默认来监听的

我们找到我们的AppBar

  1. 使用appBar里面去自己定义一个 返回按钮

它有一个属性leading

所以我们修改一下

import "package:flutter/material.dart";

class HYDetailScreen extends StatelessWidget {
  final String _message;

  HYDetailScreen(this._message);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("detail"),
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () {
            Navigator.of(context).pop(" a detial message");
          },
        ),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(_message, style: TextStyle(fontSize: 30)),
            RaisedButton(
              child: Text("回到首页"),
              onPressed: () => _backToHome(context),
            )
          ],
        ),
      ),
    );
  }

  void _backToHome(BuildContext context) {
    print("回到首页");
    Navigator.of(context).pop(" a detial message");
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQv7uVAW-1586237096421)(181D0B531F0A40F18E798871EDC2F25C)]

  1. 但是如果我们现在就想监听它原来的这个按钮的点击

这个东西就会麻烦一点

我们需要给这个Scaffold包裹一个Widget WillPopScope

它有两个必传的参数 child 就是Scaffold 和 onWillPop这个东西

import "package:flutter/material.dart";

class HYDetailScreen extends StatelessWidget {
  final String _message;

  HYDetailScreen(this._message);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () {
//        这里写true 写false是有区别的
        return Future.value(true);
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text("detail"),
          leading: IconButton(
            icon: Icon(Icons.arrow_back),
            onPressed: () {
              Navigator.of(context).pop(" a detial message");
            },
          ),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text(_message, style: TextStyle(fontSize: 30)),
              RaisedButton(
                child: Text("回到首页"),
                onPressed: () => _backToHome(context),
              )
            ],
          ),
        ),
      ),
    );
  }

  void _backToHome(BuildContext context) {
    print("回到首页");
    Navigator.of(context).pop(" a detial message");
  }
}

这个东西为true的时候给他是可以自动返回的

flutter 帮助我们执行返回操作

      onWillPop: () {
//        这里写true 写false是有区别的
        return Future.value(true);
      },

当我们为false的时候

      onWillPop: () {
//        这里写true 写false是有区别的
        return Future.value(false);
      },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajAZfF11-1586237096422)(3068070DB1C64459AB7D8E53C2219F2A)]

就是自行写返回代码

      onWillPop: () {
        _backToHome(context);
//        这里写true 写false是有区别的
        return Future.value(false);
      },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIeaLswp-1586237096423)(1420BAD567FE489EBAFD167374035EC9)]

这样就可以做一个监听

这个就是路由的跳转 和 参数的传递

路由表

为什么要有路由表

我们现在如果要在多个页面都使用一个页面

我们要在多个页面都需要这个详情页

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sd2W4pIT-1586237096423)(6A04E6D717D041C0814A24252B588E6B)]

那么我们就需要在每个地方都写这样一大段代码

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
  }

这样就太麻烦了

所以这里我们就可以使用路由映射表

我们可以给它搞一个名字 让他映射一个页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm7p8BZH-1586237096424)(F1870B55C0AA49489167329AACB3586C)]

后面我们在用的时候我们就用名字来进行跳转就可以了

我们整一个页面

about.dart

import "package:flutter/material.dart";

class HYAboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("关于页"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text("1111"),
            RaisedButton(
              child: Text("跳转"),
              onPressed: () {

              },
            )
          ],
        ),
      ),
    );
  }
}

然后在

main.dart里面跳转

class _HYHomePageState extends State<HYHomePage> {
  String message  = "default";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail(context),
            ),
            RaisedButton(
              child: Text("跳转到关于页面页面"),
              onPressed: () => _jumpToAbout(context),
            ),
            Text(message, style: TextStyle(fontSize: 20))
          ],
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
      message = result;
    });
  }

  void _jumpToAbout(BuildContext context) {
    Navigator.of(context).pushNamed("/about");
  }
}

Navigator.of(context).pushNamed("/about"); 这个地方就可以进行跳转

当然我们这里没有配置这个 /about 所以也就没有办法 跳转

我们来到MyApp

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: ,
      home: HYHomePage(),
    );
  }
}

这个MaterialApp可以传一个参数routes很明显它是要传很多的路由

所以路由表多半就是在这里传了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiuNJUw6-1586237096425)(7C9C989802CC41679229BD2B61F1C7A4)]

它这里要传的参数是一个map 所以就传一个{}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        "/about": (ctx) => HYAboutPage()
      },
      home: HYHomePage(),
    );
  }
}

这样我们就把这个about的路由映射配置好了

那我们的首页 也是可以之直接写在这里的

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        "/": (ctx) => HYHomePage(),
        "/about": (ctx) => HYAboutPage()
      },
      home: HYHomePage(),
    );
  }
}

之前我们的Material里面是有一个home属性的

这个东西我们都是可以不写的

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        "/": (ctx) => HYHomePage(),
        "/about": (ctx) => HYAboutPage()
      },
      initialRoute: "/",
    );
  }
}

这样我们就可以 默认启动homepage的页面了

所以我们的代码可以这样来写

我们来试一下看看其他的路由跳转能否实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aS7pbdkX-1586237096425)(C48EFFEE1E1149DB83EF0E8196E3E467)]

跳转回来也很容易

import "package:flutter/material.dart";

class HYAboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("关于页"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text("1111"),
            RaisedButton(
              child: Text("跳转"),
              onPressed: () {
                Navigator.of(context).pop();
              },
            )
          ],
        ),
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ACDhnTfS-1586237096426)(3872B1E0718B40A382839C6BED4CC867)]

但是我们这里 很多的地方我们都使用了很多的字符串常量 就是那个我们用于跳转的地方 路由的地址

我们很多的地方 都有这个东西 我们很容易写错这个东西

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JDYpwYXv-1586237096426)(A0EA6F7669254F05966FDE1C47F3A2B0)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrS6piJE-1586237096427)(580EE490430D4FE0ADE000CDB4053471)]

虽然来说还是不容易写错但是 你还是不能保证他一定不会出错

所以那我们在开发的时候 我们会把这个东西定义成常量然后放到一个文件里面

我们就会在对应的页面里面 写一个静态的变量来保存这个 路由地址的变量这样 有系统检错 就不容易出错

about.dart

class HYAboutPage extends StatelessWidget {
  static final String routeName = "/about";

  @override
  Widget build(BuildContext context) {
    return Scaffold(

然后使用的时候我们

main.dart

...
      routes: {
        "/": (ctx) => HYHomePage(),
        HYAboutPage.routeName: (ctx) => HYAboutPage()
      },
...
  void _jumpToAbout(BuildContext context) {
    Navigator.of(context).pushNamed(HYAboutPage.routeName);
  }
}

同样main这些路由我们都可以这样配置

首页的路由我们也可以这样配置

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        HYHomePage.routeName: (ctx) => HYHomePage(),
        HYAboutPage.routeName: (ctx) => HYAboutPage()
      },
      initialRoute: "/",
    );
  }
}

class HYHomePage extends StatefulWidget {
  static final String routeName = "/";

这种就是开发里面约定俗成的代码规范

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4I4HhsOM-1586237096428)(35097C9C952C4D7BB93EEEDB87DE261C)]

如果我们通过这种pushNamed 需要传递一些参数 那么我们要怎么传呢

那这个参数怎么传呢

我们之前是通过构造器来传递参数的 但是现在已经不能通过构造器来传递参数了

更根本都没有调用 构造函数

  • 我们可以通过这个arguments 来拿到这个参数了
  void _jumpToAbout(BuildContext context) {
    Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message PushNamed");
  }

那么我们怎么拿到这个数据呢

我们可以通过

ModalRoute.of(context).setting.arguments拿到对应的参数

如果你确定他的类型的话你还可以转化过来

Modal.of(context).setting.arguments as String 拿到这个参数

这个东西他默认情况下获得的是要给Object类型

你想要转化的话 还需要as String 来转化

about.dart

import "package:flutter/material.dart";

class HYAboutPage extends StatelessWidget {
  static final String routeName = "/about";

  @override
  Widget build(BuildContext context) {
    //    如果你确定这个东西是一个String类型的东西 我们就可以通过这个方式来拿到对应的参数
    final message = ModalRoute.of(context).settings.arguments as String;

    return Scaffold(
      appBar: AppBar(
        title: Text("关于页"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(message),
            RaisedButton(
              child: Text("跳转"),
              onPressed: () {
                Navigator.of(context).pop();
              },
            )
          ],
        ),
      ),
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNzguJdJ-1586237096428)(A885DAE81C134946846BDFF9FCD13AF1)]

这个东西就是我们的命名路由

也是我们的命名路由怎么传递参数

他怎么返回呢

一样的 这个方法也会返回 Future

import "package:flutter/material.dart";
import 'package:learn_flutter02/day11_route_Protice/about.dart';
import 'package:learn_flutter02/day11_route_Protice/detail.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        HYHomePage.routeName: (ctx) => HYHomePage(),
        HYAboutPage.routeName: (ctx) => HYAboutPage()
      },
      initialRoute: "/",
    );
  }
}

class HYHomePage extends StatefulWidget {
  static final String routeName = "/";

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

class _HYHomePageState extends State<HYHomePage> {
  String message  = "default";

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail(context),
            ),
            RaisedButton(
              child: Text("跳转到关于页面页面"),
              onPressed: () => _jumpToAbout(context),
            ),
            Text(message, style: TextStyle(fontSize: 20))
          ],
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
      message = result;
    });
  }

  void _jumpToAbout(BuildContext context) {
    Future resFuture = Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message PushNamed");
    resFuture.then((res) {
      print("获得传递的参数值:$res");
    });
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLLbfLkO-1586237096429)(32ADF431913845F3AEA656C8CC2FE068)]

这个代码是一样的 这个 就是命名路由传递参数的方式

  • 将Detail也使用命名路由的方式跳转

那简单啊

但是 我们之前是给这个Detail写了一个构造函数的 这里参数还不能乱传

所以对于我们的Detail就不能用命名路由的方式进行跳转

你想要通过命名路由的方式跳转 就不行

当然如果我们把detail修改了 写的和about一样没有带参构造

那肯定是可以的

但是我们希望在不改变原来的基础上 设置这个路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0uPJrQcA-1586237096429)(E6CAD2FD14604211A2DEB4C7F9673BC2)]

我们可以在这里找到一个钩子函数 当你的路由在这里找不到的时候

onGenerateRoute

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VqXqBhq-1586237096430)(999DFF993378468A8D7087F7F9A5ABBD)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nK6y2psg-1586237096432)(E171FE099A764140BE6DD3083D77115C)]

可以看到它需要传递的参数 然后要返回一个Route 但是我们这里又不是只有一个路由

你得根据不同的名字返回不同的页面 所以需要判断

      onGenerateRoute: (setting) {
        if( setting.name == HYDetailScreen.routeName ) {
          return MaterialPageRoute(
            builder: (ctx) {
              return HYDetailScreen();
            }
          );
        }
      },

但是不对啊 这里还是有参数

那你就跟参数啊

  void _jumpToAbout(BuildContext context) {
    Future resFuture = Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message PushNamed");
    resFuture.then((res) {
      print("获得传递的参数值:$res");
    });
  }

  void _jumpToDetail2(BuildContext context) {
//    按以前的方式 我们就这样
    Navigator.of(context).pushNamed(HYDetailScreen.routeName, arguments: "a home Detail2 Message");
  }
}

同样传递这个参数 然后拿到对应的参数

      onGenerateRoute: (settings) {
        if( settings.name == HYDetailScreen.routeName ) {
          return MaterialPageRoute(
            builder: (ctx) {
              return HYDetailScreen(settings.arguments);
            }
          );
        }
        return null;
      },

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LkUFHuB0-1586237096433)(AAB62767306A43428C1A479978157121)]

这个东西就可以使用带有构造函数参数 不改变结构的情况下使用 命名路由

import "package:flutter/material.dart";
import 'package:learn_flutter02/day11_route_Protice/about.dart';
import 'package:learn_flutter02/day11_route_Protice/detail.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        HYHomePage.routeName: (ctx) => HYHomePage(),
        HYAboutPage.routeName: (ctx) => HYAboutPage(),
      },
      initialRoute: "/",
      onGenerateRoute: (settings) {
        if( settings.name == HYDetailScreen.routeName ) {
          return MaterialPageRoute(
            builder: (ctx) {
              return HYDetailScreen(settings.arguments);
            }
          );
        }
        return null;
      },
    );
  }
}

class HYHomePage extends StatefulWidget {
  static final String routeName = "/";

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

class _HYHomePageState extends State<HYHomePage> {
  String message  = "default";

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail(context),
            ),
            RaisedButton(
              child: Text("跳转到关于页面页面"),
              onPressed: () => _jumpToAbout(context),
            ),
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail2(context),
            ),
            Text(message, style: TextStyle(fontSize: 20))
          ],
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
      message = result;
    });
  }

  void _jumpToAbout(BuildContext context) {
    Future resFuture = Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message PushNamed");
    resFuture.then((res) {
      print("获得传递的参数值:$res");
    });
  }

  void _jumpToDetail2(BuildContext context) {
//    按以前的方式 我们就这样
    Navigator.of(context).pushNamed(HYDetailScreen.routeName, arguments: "a home Detail2 Message");
  }
}
  • 错误页面的设置

如果我们这里压根都没有这个页面跳转会怎么样呢

            RaisedButton(
              child: Text("跳转到设置页面"),
              onPressed: () => _jumpToSetting(context),
            ),
...

...
  void _jumpToSetting(BuildContext context) {
    Navigator.of(context).pushNamed("setting");
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CIJmV8YN-1586237096433)(C128BBD6B4AE4A9F804D15168730EBC9)]

它报错了

它这里直接就报错了 如果确实有这种情况 我们希望它报错吗 不希望 我们希望它跳转到一个错误页面

我们可以定制一个错误页面

error.dart

import "package:flutter/material.dart";

class HYErrorScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("error")
      ),
      body: Center(
        child: Text("错误页面", style: TextStyle(fontSize: 20))
      ),
    );
  }
}

这个东西怎么配置呢 它就需要另外一个钩子函数了

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: {
        HYHomePage.routeName: (ctx) => HYHomePage(),
        HYAboutPage.routeName: (ctx) => HYAboutPage(),
      },
      initialRoute: "/",
      onGenerateRoute: (settings) {
        if( settings.name == HYDetailScreen.routeName ) {
          return MaterialPageRoute(
            builder: (ctx) {
              return HYDetailScreen(settings.arguments);
            }
          );
        }
        return null;
      },
      onUnknownRoute: (setting) {
//        这个地方一般不会做很多的判断
        return MaterialPageRoute(
          builder: (ctx) {
            return HYErrorScreen();
          }
        );
      },
    );
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYMDkjHJ-1586237096434)(8F12D5ABCF5340ADA277F1E18A79E733)]

  • 抽离代码

但是我们这里代码 太过于集中 而且不好看

MyApp里面东西太多了

所以抽离代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bsmTlP07-1586237096435)(DCFCD86FF3714A66850E90D28DEEADA8)]

逐渐把代码抽离过了来

  • alt + enter 点到对应的地方可以自动导包
import '../about.dart';
import '../main.dart';

class HYRouter {
  static final Map<String, > routes ={
    HYHomePage.routeName: (ctx) => HYHomePage(),
    HYAboutPage.routeName: (ctx) => HYAboutPage(),
  };
}

这样我们就可以把所有要管理路由的东西放到router.dart里面管理了

交到一个文件里面管理就有几个好处

  • 一个交过去以后MyApp里面代码变少了
  • 第二个我们要管理 直接到文件里改了 就不要每个东西 都在MyApp里面加了

这个东西就是一个代码的封装

import "package:flutter/material.dart";
import 'package:learn_flutter02/day11_route_Protice/about.dart';
import 'package:learn_flutter02/day11_route_Protice/detail.dart';
import 'package:learn_flutter02/day11_route_Protice/error.dart';
import 'package:learn_flutter02/day11_route_Protice/router/router.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue, splashColor: Colors.transparent,
      ),
      routes: HYRouter.routes,
      initialRoute: HYRouter.initialRoute,
      onGenerateRoute: HYRouter.generateRoute,
      onUnknownRoute: HYRouter.unknownRoute,
    );
  }
}

class HYHomePage extends StatefulWidget {
  static final String routeName = "/";

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

class _HYHomePageState extends State<HYHomePage> {
  String message  = "default";

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail(context),
            ),
            RaisedButton(
              child: Text("跳转到关于页面页面"),
              onPressed: () => _jumpToAbout(context),
            ),
            RaisedButton(
              child: Text("跳转到详情页面"),
              onPressed: () => _jumpToDetail2(context),
            ),
            RaisedButton(
              child: Text("跳转到设置页面"),
              onPressed: () => _jumpToSetting(context),
            ),
            Text(message, style: TextStyle(fontSize: 20))
          ],
        ),
      ),
    );
  }

  void _jumpToDetail(context) {
//    1. 这个是要给普通的跳转方式
    Future res =  Navigator.of(context).push(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return HYDetailScreen("a home message");
        }
      )
    );
    res.then((result) {
      print("首页获得的参数 $result");
      message = result;
    });
  }

  void _jumpToAbout(BuildContext context) {
    Future resFuture = Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message PushNamed");
    resFuture.then((res) {
      print("获得传递的参数值:$res");
    });
  }

  void _jumpToDetail2(BuildContext context) {
//    按以前的方式 我们就这样
    Future resFuture = Navigator.of(context).pushNamed(HYDetailScreen.routeName, arguments: "a home Detail2 Message");
    resFuture.then((res) {
      print(res);
    });
  }

  void _jumpToSetting(BuildContext context) {
    Navigator.of(context).pushNamed("setting");
  }
}

router.dart

import 'package:flutter/material.dart';

import '../about.dart';
import '../detail.dart';
import '../error.dart';
import '../main.dart';

class HYRouter {
  static final Map<String, WidgetBuilder> routes ={
    HYHomePage.routeName: (ctx) => HYHomePage(),
    HYAboutPage.routeName: (ctx) => HYAboutPage(),
  };

  static final String initialRoute = "/";

  static final RouteFactory generateRoute = (settings) {
    if( settings.name == HYDetailScreen.routeName ) {
      return MaterialPageRoute(
          builder: (ctx) {
            return HYDetailScreen(settings.arguments);
          }
      );
    }
    return null;
  };

  static final RouteFactory unknownRoute = (setting) {
//        这个地方一般不会做很多的判断
    return MaterialPageRoute(
        builder: (ctx) {
          return HYErrorScreen();
        }
    );
  };
}

文件结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVCfCPni-1586237096435)(0CCA81A5AAAB473FB23C8E11A1EF2C53)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值