知其然,也要知其所以然。最近的搬砖工作中,开发ui页面都是运用flutter,android原生只沦为了后台逻辑处理的后台。在搬砖过程中,往往只要知道怎么用,便能搭起小房子,而要建的恢宏又大气,还是少不了对于原理的学习。 在接触flutter中,Widget是咱们接触最多的类。咱们对于各种界面的建立用的便是各式各样的Widget,有StatefullWidgetStatelessWidget。印象中Widget便是最为ui建立而存在的,然而在flutter的中,还有一种widget不负责ui控制,但却十分重要,那便是InheritedWidget。 在咱们常用的第三方数据同享库中,都有InheritedWidget的影子,比如下面两个。

  • flutter_bloc
  • Provider 还有开发中用到的许多数据,比如Theme.of(context),DefaultTextStyle.of(context)等,都是在父节点配置,子widget全局获取。这都离不开InheritedWidget的特性。 既然如此重要,今日就好好学习下InheritedWidget的大致原理吧。

一,InheritedWidget 介绍

InheritedWidgetFlutter 中非常重要的一个功能型组件,它供给了一种在 widget 树中从上到下同享数据的办法,比如咱们在运用的根 widget 中经过InheritedWidget同享了一个数据,那么咱们便能够在任意子widget 中来获取该同享的数据!这个特性在一些需要在整个 widget 树中同享数据的场景中非常便利!如Flutter SDK中正是经过 InheritedWidget 来同享运用主题(Theme)和 Locale (当前语言环境)信息的。

InheritedWidget和 React 中的 context 功能相似,和逐级传递数据比较,它们能完成组件跨级传递数据。InheritedWidget的在 widget 树中数据传递方向是从上到下的,这和告诉Notification的传递方向正好相反。

二,组件树中的数据同享用法(运用 ValueNotrifier 和 InhertitedWidget 完成简略的状况办理)

  1. 咱们首先创立一个MyProvider 承继自InheritedWidget,以寄存状况数据,该状况承继自ChangeNotifier,即代码中的model变量。
import 'package:flutter/widgets.dart';
///供给状况存储
class MyProvider<T extends ChangeNotifier> extends InheritedWidget {
  T model;
  MyProvider({Key? key,required Widget child,required this.model}) : super(key: key,child:child);
  @override
  Widget build(BuildContext context) {
    return this.child;
  }
  static MyProvider<T>? of<T extends ChangeNotifier>(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyProvider<T>>();
  }
  @override
  bool updateShouldNotify(covariant MyProvider oldWidget) {
    return false;
  }
}
  1. 接下来创立一个Customer,用来完成动态改写,和部分改写,原理主要是运用changeNotifier监听器更新数据更新,注册对应监听器改写Customer的子布局。
///供给状况消费,在状况ValueNotifier更新的时分,主动改写Customer组件
class Customer<T extends ChangeNotifier> extends StatefulWidget{
  const Customer({required this.builder,});
  final ProviderBuilder<T> builder;
  @override
  State<Customer<T>> createState() => _CustomerState<T>();
}
class _CustomerState<T extends ChangeNotifier> extends State<Customer<T>> {
  late MyProvider? provider;
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    provider=MyProvider.of<T>(context);
    if(provider==null) throw "请在先人节点供给MyProvider<T extends ChangeNotifier>";
    provider!.model.addListener(() {
      setState(()=>{});
    });
  }
  @override
  Widget build(BuildContext context) {
    return widget.builder(context,MyProvider.of<T>(context)!.model);
  }
}
typedef ProviderBuilder<T extends ChangeNotifier> = Widget Function(BuildContext context,T value);
  1. 接着创立一个展现用的widget,包括显现数据的子widget 和 点击改写数据的 按钮,代码如下:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:free/widget/my_provider.dart';
import 'package:provider/provider.dart';
class TestWidget extends StatefulWidget {
  const TestWidget({Key? key}) : super(key: key);
  @override
  State<TestWidget> createState() => _TestWidgetState();
}
class TestModel extends ChangeNotifier{
  var num=0;
  TestModel() : super();
}
class _TestWidgetState extends State<TestWidget> {
   var model=TestModel();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MyProvider<TestModel>(
        model:model,
        child: Builder(
          builder: (context) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  MyTextWidget(),
                  MaterialButton(
                    color: Colors.blueAccent,
                      elevation: 10,
                      onPressed: () {
                        model.num++;
                        model.notifyListeners();
                      },
                      child: Text("点击加一")),
                ],
              ),
            );
          }
        ),
      ),
    );
  }
}
class MyTextWidget extends StatelessWidget {
  const MyTextWidget ({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Customer<TestModel>(builder:(c,value) => Text("${value.num}"));
  }
}

至此,运用InheritedWidgetChangeNotifier完成的简易状况办理库就完成了。该库完成了跨组件同享数据,跨组件更新数据状况,并更新对应的兄弟组件。 这也是一些第三方开源状况库的基本原理。

【flutter 起步走】flutter共享数据利器,InheritedWidget原理探秘!

三,InheritedWidget如何同享数据

先看看咱们获取 InheritedWidget实例的dependOnInheritedWidgetOfExactType办法,注释翻译如下:

获取给定类型T的最近小部件,它有必要是详细InheritedWidget子类的类型,并将此构建上下文注册到该小部件,以便当该小部件更改时(或引入该类型的新小部件,或小部件消失脱离),这个构建上下文被重建,以便它能够从那个小部件获取新值。 这通常从of()静态办法中隐式调用,例如Theme.of 。 不该从小部件结构函数或State.initState办法调用此办法,由于假如承继的值产生更改,这些办法将不会再次被调用。为了保证小部件在承继值更改时正确更新本身,只能从构建办法、布局和绘制回调或从State.didChangeDependencies调用(直接或间接)。 不该从State.dispose调用此办法,由于此刻元素树不再稳定。要从该办法引用先人,请将对先人的引用保存在State.didChangeDependencies中。从State.deactivate中运用此办法是安全的,每逢从树中删除小部件时都会调用该办法。 也能够从交互事情处理程序(例如手势回调)或计时器调用此办法,以获取一次值,假如该值不会被缓存并稍后重用。 调用此办法是 O(1),具有较小的常数因子,但会导致更频频地重建小部件。 一旦小部件经过调用此办法注册对特定类型的依靠关系,它将被重建,并且State.didChangeDependencies将被调用,每逢与该小部件相关的更改产生时,直到下一次移动小部件或其先人之一(例如例如,由于添加或删除了先人)。 aspect参数仅在T是支持部分更新的InheritedWidget子类时运用,例如InheritedModel 。它指定此上下文所依靠的承继小部件的“方面。 T? dependOnInheritedWidgetOfExactType({ Object? aspect });

  1. 咱们找到了BuildContext的该办法的详细完成,实际上是在Element中,其完成了BuildContext
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

能够看到主要是从 **_inheritedWidgets** 中找到对应TypeInheritedElement

  1. 咱们再去看InheritedElement源码,主要关注下_updateInheritance办法
@override
void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
  if (incomingWidgets != null)
    _inheritedWidgets = HashMap<Type, InheritedElement>.of(incomingWidgets);
  else
    _inheritedWidgets = HashMap<Type, InheritedElement>();
  _inheritedWidgets![widget.runtimeType] = this;
}

小结:能够看到_updateInheritance办法中,其向_inheritedWidgets中添加了自己。而_inheritedWidgetsElement树中会被子Element承继,所以子树就能轻松获取到该InheritedElement实例了。 而_updateInheritance办法,是在Element mountactive办法中被调用的,当**InheritedElement**被挂载到element树中或者被激活后执行,就能被子element获取到了。 至此就能大约了解**InheritedWidget** 如何同享数据了,主要是运用InhritedElement作为中转持有。

最终:

最终咱们大约了解了InheritedWidget的运用和原理。但是要进一步整理整个流程的时分,就需要整理Widget,Element和RenderObject三者的关系了。 学无止境,下一把就来了解下flutter的Widget体系。 加油!!