从零开始的Flutter之旅: InheritedWidget

往期回顾

从零开始的Flutter之旅: StatelessWidget

从零开始的Flutter之旅: StatefulWidget

在之前的文章中,介绍了StatelessWidget与StatefulWidget的特性与它们的呈现原理。

这期要聊的是它们的另一个兄弟InheritedWidget。

特性

InheritedWidget是Flutter中的一个非常重要的功能组件,它能够提供数据在widget树中从上到下进行传递。保证数据在不同子widget中进行共享。这对于一些需要使用共享数据的场景非常有效,例如,在Flutter SDK中就是通过InheritedWidget来共享应用的主题与语言信息。

可能你还有点模糊,别急,下面我们通过一个简单的示例来了解InheritedWidget。

示例

相信开始学Flutter时都看过官方的计数器示例。我们将官方提供的计数器示例使用InheritedWidget进行改造。

首先我们需要一个CountInheritedWidget,它继承于InheritedWidget。

class CountInheritedWidget extends InheritedWidget {
  CountInheritedWidget({@required this.count, Widget child})
      : super(child: child);
 
  // 共享数据,计数的数量
  final int count;
 
  // 统一的获取CountInheritedWidget实例, 方便树中子widget的获取共享数据
  // 必须在State中调用才会有效
  static CountInheritedWidget of(BuildContext context) {
    // 调用共享数据的子widget将不会回调didChangeDependencies方法,即子widget将不会更新
    // return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
    return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
  }
 
  // true -> 通知树中依赖改共享数据的子widget
  @override
  bool updateShouldNotify(CountInheritedWidget oldWidget) {
    return oldWidget.count != count;
  }
}
  1. 在CountInheritedWidget中提供共享计数的数量count
  2. 同时为外部提供统一的获取CountInheritedWidget实例的of方法
  3. 最后再重写updateShouldNotify方法,来通知依赖该共享count的子widget进行更新

现在已经有了共享数据count的提供,接下来是在具体的子widget中进行使用。

我们抽离出一个CountText子widget

class CountText extends StatefulWidget {
  @override
  _CountTextState createState() {
    return _CountTextState();
  }
}
 
class _CountTextState extends State<CountText> {
  @override
  Widget build(BuildContext context) {
    return Text("count: ${CountInheritedWidget.of(context).count.toString()}");
  }
 
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}
  1. 内部引用了CountInheritedWidget中的共享数据count,通过of方法获取CountInheritedWidget实例
  2. didChangeDependencies可以用来监听子widget依赖是否反生改变

最后,我们再将CountInheritedWidget与CountText结合起来,通过简单的点击自增事件来看下效果

class CountWidget extends StatefulWidget {
  @override
  _CountState createState() {
    return _CountState();
  }
}
 
class _CountState extends State<CountWidget> {
  int count = 0;
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Count App',
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(
          title: Text("Count"),
        ),
        body: Center(
          child: CountInheritedWidget(
            count: count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                CountText(),
                RaisedButton(
                  onPressed: () => setState(() => count++),
                  child: Text("Increment"),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

上面的层级关系是CountText刚好是CountInheritedWidget的子widget。

现在我们通过点击事件直接改变外部的count值,如果InheritedWidget从上到下数据传到的效果能够生效,那么在CountText中引用的count将会与外部count同步,程序呈现的效果将会是自增的,同时由于依赖的count发生改变CountText中的didChangeDependencies也会回调。

我们直接运行一下

点击后的输出日志

I/flutter: didChangeDependencies

说明InheritedWidget的效果已经生效,通过InheritedWidget的使用,我们可以很方便的在嵌套下层的子widget中拿到上层的数据,或者说整个widget的共享数据。

分析

在依赖方式改变时子widget的didChangeDependencies会回调,但由于你可能会在该方法中做一些特殊的操作,例如网络请求。只是需要一次就可以。如果是套用我们上面的示例,将会在count子增时反复调用。

为了防止didChangeDependencies的调用,我们再来看CountInheritedWidget的of方法中注释的那部分

  static CountInheritedWidget of(BuildContext context) {
    // 调用共享数据的子widget将不会回调didChangeDependencies方法,即子widget将不会更新
    // return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
    return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
  }

我们使用的是dependOnInheritedWidgetOfExactType方法,依赖的共享数据发生改变时会回调子widget中的didChangeDependencies方法,如果我们不想要子widget调用该方法,可以使用注释的代码,通过getElementForInheritedWidgetOfExactType方法来获取共享数据。

如果此时我们再运行一下项目,点击count自增,控制台将不会再输出日志。这样就可以解决didChangeDependencies的反复调用。

而这两个方法的主要区别是在dependOnInheritedWidgetOfExactType调用的过程中会进行注册依赖关系

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

所以dependOnInheritedWidgetOfExactType更新依赖的子widget中的didChangeDependencies方法。

思考下一个问题,虽然现在didChangeDependencies方法不会调用,但是CountText的build方法还是会执行。原因是在CountWidget中通过setState来改变count值,会重新build所用的子widget。但我们真正想要的只是更新子widget中依赖的CountInheritedWidget的组件值。

那么如何解决呢?这里提供一个解决方案是为子widget提供缓存。可以通过封装一个简单的StatefulWidget,将子widget缓存起来。如果对这块感兴趣的,可以期待我的后续文章。

推荐项目

下面介绍一个完整的Flutter项目,对于新手来说是个不错的入门。

flutter_github,这是一个基于Flutter的Github客户端同时支持Android与IOS,支持账户密码与认证登陆。使用dart语言进行开发,项目架构是基于Model/State/ViewModel的MSVM;使用Navigator进行页面的跳转;网络框架使用了dio。项目正在持续更新中,感兴趣的可以关注一下。

当然如果你想了解Android原生,相信flutter_github的纯Android版本AwesomeGithub是一个不错的选择。

如果你喜欢我的文章模式,或者对我接下来的文章感兴趣,建议您关注我的微信公众号:【Android补给站】

或者扫描下方二维码,与我建立有效的沟通,同时更快更准的收到我的更新推送。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351