flutter系列InheritedWidget介绍

简介: InheritedWidget介绍在Flutter进行界面开发时,我们经常会遇到数据传递的问题。由于Flutter采用节点树的方式组织页面,以致于一个普通页面的节点层级会很深。此时,我们如果还是一层层传递数据,当需要获取多层父节点的数据时,会非常麻烦。 因为出现上述问题,Flutter给我我们提供一种InheritedWidget,InheritedWidget能够让节点下的所有子节点,访问该节点下的数据。 关于Scoped Model、BloC、Provider就是基于InheritedWidget实现的。

InheritedWidget介绍

在Flutter进行界面开发时,我们经常会遇到数据传递的问题。由于Flutter采用节点树的方式组织页面,以致于一个普通页面的节点层级会很深。此时,我们如果还是一层层传递数据,当需要获取多层父节点的数据时,会非常麻烦。 因为出现上述问题,Flutter给我我们提供一种InheritedWidget,InheritedWidget能够让节点下的所有子节点,访问该节点下的数据。 关于Scoped Model、BloC、Provider就是基于InheritedWidget实现的。

InheritedWidget源码分析

可以看到InheritedWidget的源码非常简单。

/// 抽象类,继承自Proxywidget 继承路径InheritedWidget => ProxyWidget => Widget
abstract class InheritedWidget extends ProxyWidget {
  /// 构造函数
  /// 因为InheritedWidget是没有界面的Widget,所有需要传入实际的Widget 
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);
  /// 重写了超类Widget createElement方法
  @override
  InheritedElement createElement() => InheritedElement(this);
  /// 父级或祖先widget中改变(updateShouldNotify返回true)时会被调用。
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

InheritedWidget示例

import 'package:flutter/material.dart';
import 'package:flutter_code/InheritedWidget/InheritedState.dart';
class InheritedCount extends StatefulWidget {
  @override
  _InheritedCountState createState() => _InheritedCountState();
}
class _InheritedCountState extends State<InheritedCount> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedDemo"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Icon(Icons.add, color: Colors.white,),
      ),
      body: Center(
        child: InheritedState(
            count: _count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                WidgetA(),
                WidgetB()
              ],
            )
        ),
      ),
    );
  }
}
class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("widget text");
  }
}
class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(InheritedState.of(context)?.count.toString(),
      style: TextStyle(
        color: Colors.green,
        fontSize: 50
      ),  
    );
  }
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class InheritedState extends InheritedWidget {
  /// 构造方法
  InheritedState({
    Key key,
    @required this.count,
    @required Widget child
  }): assert(count != null),
    super(key:key, child: child);
  /// 需要共享的数据
  final int count;
  /// 获取组件最近当前的InheritedWidget
  static InheritedState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedState>();
  }
  /// 通知依赖该树共享数据的子widget
  @override
  bool updateShouldNotify(covariant InheritedState oldWidget) {
    return count != oldWidget.count;
  }
}

InheritedWidget源码分析

在上面的计数器示例代码中,WidgetB和InheritedWidget发生关联的就是InheritedState.of(context)?.count.toString(),其中最关键的方式是context.dependOnInheritedWidgetOfExactType(),我们查看dependOnInheritedWidgetOfExactType()在Element中的源码如下:该代码是在framework.dart 第3960行

Map<Type, InheritedElement> _inheritedWidgets;
@override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
    /// 断言,用于在调试状态下检测是否有正在使用(激活)的祖先
    assert(_debugCheckStateIsActiveForAncestorLookup());
    /// 获取到_inheritedWidgets数组数据
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      // 断言,判断当前ancestor是否是InheritedElement类型
      assert(ancestor is InheritedElement);
      // 返回并调用更新方法
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

我们不难看出,每一个Element实例都会持有一个_inheritedWidgets,调用次用该方法时会从改集合对象中取出相关类型的InheritedElement实例,那么在这个方法中我们没有看到设置_inheritedWidgets的方法,我们来查看一下_inheritedWidgets是如何赋值的。

// Element  
void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

我们找到赋值是在_updateInheritance方法中,首先断言当前节点是否激活,然后通过父节点的_inheritedWidgets进行赋值,我们继续来看_updateInheritance什么情况下会调用:

 @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    ......
    _updateInheritance();
    ......
  }
 @mustCallSuper
  void activate() {
    ......
    _updateInheritance();
    ......
  }

我们可以看到在Element中它在mount和activate函数执行了调用,也就是说element每次挂载和重新时,会调用该方法。那么当该方法执行的时候,element就会从上层中拿到所有的InheritedElement。而InheritedElement他最终继承了Element,并可以看到InheritedElement重写了_updateInheritance方法:

 @override
  void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

InheritedWidget是如何进行刷新的

前面我们分析到InheritedElement会拿到父类的所有的InheritedElment并向下传递,而InheritedWidget正是通过这种方法才能让下面的子Widget能访问的上层中所有的InheritedWidget,那么它是如何进行刷新的呢?我们在Element的dependOnInheritedWidgetOfExactType方法中调用了dependOnInheritedElement方法,代码如下:

Set<InheritedElement> _dependencies;
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
  assert(ancestor != null);
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies.add(ancestor);
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget;
}
@protected
void updateDependencies(Element dependent, Object aspect) {
  setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
  _dependents[dependent] = value;
}

可以看到InheritedElement实例调用自己的updateDependencies方法并将当前的Element实例传递过去

 /// Called during build when the [widget] has changed.
  ///
  /// By default, calls [notifyClients]. Subclasses may override this method to
  /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
  /// widgets are equivalent).
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

由于当InheritedElement更新时,会执行updated方法,然后继续调用notifyClients,遍历所有的element并调用didChangeDependencies方法。

相关文章
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
306 2
【每日 widget】 Flutter InheritedWidget
【每日 widget】 Flutter InheritedWidget
291 0
【每日 widget】 Flutter InheritedWidget
|
12月前
flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
212 1
|
11月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
698 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
12月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
423 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
11月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
728 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
12月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
407 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
12月前
|
前端开发 Java 开发工具
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
1037 18
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
|
11月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
357 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
12月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
549 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程