scoped_model 源码阅读

栏目: ASP.NET · 发布时间: 7年前

内容简介:我们先来看一下

我们先来看一下 InheritedWidget 是如何实现共享数据的.

以下是官网上的一个小例子

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);

  final int data; //需要在子树中共享的数据,保存点击次数

  //定义一个便捷方法,方便子树中的widget获取共享数据  
  static ShareDataWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ShareDataWidget);
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget
    //的子widget的`state.didChangeDependencies`会被调用
    return old.data != data;
  }
}

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依赖ShareDataWidget
            ),
            RaisedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}
复制代码

这里需要注意:

  • context.inheritFromWidgetOfExactType(ShareDataWidget) 来获取到指定 InheritedWidget 中的数据, 实际通知的时候是父Widget往下传递还是子Widget往上遍历呢?

接下lai 探索原因

InheritedWidget 的源码很简单, 继承了 ProxyWidget , 也没有实现太多逻辑, 对 createElement 进行了实现, 还定义了 updateShouldNotify , 该方法的意思就是更新的时候是否应该通知在 build 阶段通过 inheritFromWidgetOfExactType 查找该 Widget 的子 Widget .

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
复制代码

顺藤摸瓜, 我们去就去看 InheritedElement(this) 做了什么, 进去之后我们可以发现 _updateInheritance 方法.

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;
  }
复制代码

同时我们对比一下普通的 Element 的该方法实现, 只是简单的将父 Element 的 _inheritedWidgets 属性保存到自身(这样就保证了父级的向子集传递特性).

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
复制代码

但是这个 _inheritedWidgets 属性又是在哪里出现呢? 它定义在 Element 中, 每一个实例都有这个属性. 它的作用是存储上级节点 Widget 和 Element 之间的映射.

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Creates an element that uses the given widget as its configuration.
  ///
  /// Typically called by an override of [Widget.createElement].
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  Element _parent;
...
Map<Type, InheritedElement> _inheritedWidgets;
复制代码

现在我们回到 InheritedElement 的实现.

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;
  }
复制代码

InheritedElement 会将自身的信息添加到 _inheritedWidgets 属性中, 然后子孙都可以通过他们自身的该属性访问当前的 InheritedElement 了.

现在我们知道如何访问_inheritedWidgets属性以及包含的内容了, 那么通知机制是如何实现呢?

一开始例子中就是使用 inheritFromWidgetOfExactType(Type) 方法去获取到指定的 InherientWidget 的, 那么这个方法是怎么实现呢?

InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }
  
  @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
复制代码

首先会获取 Element(Context) 的 _inheritedWidgets 指定类型的 Element , 如果获取到了, 则会添加到自身的依赖列表中, 祖先节点也有记录这个依赖, 这样在更新时候就可以直接通过 _dependencies 属性来进行通知了.

每一次 InherientElement 更新的时候, 都会调用 notifyClients 方法来通知子节点, 调用子节点的 didChangeDependencies 方法

void notifyClients(InheritedWidget oldWidget) {
    if (!widget.updateShouldNotify(oldWidget))
      return;
          assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (Element dependent in _dependents) {
      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));
      dependent.didChangeDependencies();
    }
  }
复制代码

以上就是 InherientWidget 的原理, 接下来就是看看 scoped_model 做了怎样的封装.

scoped_model

该库主要的类有:

abstract class Model extends Listenable

abstract class Model extends Listenable {
  final Set<VoidCallback> _listeners = Set<VoidCallback>();
  int _version = 0;
  int _microtaskVersion = 0;

  void addListener(VoidCallback listener) {
    debugPrint("添加监听器");
    listener();
    _listeners.add(listener);
  }

  @override
  void removeListener(VoidCallback listener) {
    print("监听器被去除");
    _listeners.remove(listener);
  }

  int get listenerCount => _listeners.length;

  @protected
  void notifyListeners() {
    if (_microtaskVersion == _version) {
      _microtaskVersion++;
      scheduleMicrotask(() {
        _version++;
        _microtaskVersion = _version;
        _listeners.toList().forEach((VoidCallback listener) => listener());
      });
    }
  }
}
复制代码

Model 继承了 Listenable , 值在改变的时候调用 notifyListeners 既可以通知到所有的监听器.

class ScopedModel<T extends Model> extends StatelessWidget

class ScopedModel<T extends Model> extends StatelessWidget {

  final T model;

  final Widget child;

  ScopedModel({@required this.model, @required this.child})
      : assert(model != null),
        assert(child != null);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: model,
      builder: (context, _) => _InheritedModel<T>(model: model, child: child),
    );
  }
  // 找到 ScopeModel, 并且让子节点和祖先节点建立依赖
  static T of<T extends Model>(
    BuildContext context, {
    bool rebuildOnChange = false,
  }) {
    final Type type = _type<_InheritedModel<T>>();

    Widget widget = rebuildOnChange
        ? context.inheritFromWidgetOfExactType(type)
        : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

    if (widget == null) {
      throw ScopedModelError();
    } else {
      return (widget as _InheritedModel<T>).model;
    }
  }

  static Type _type<T>() => T;
}
复制代码

通知方法

作者在构造 ScopedModel 的时候, 使用了 AnimationBuilder , 这里会注册一个监听器到 Model 中, 然后每一次值的改变都会调用 AnimationBuilder.builder 方法, 然后就会触发 InherientWidget 的改变, 根据 updateShouldNotify 来决定是否通知子孙控件更新, 但是在这里我们并没有看到 InherientWidget 的影子, 让我们接着往下看.

class _InheritedModel<T extends Model> extends InheritedWidget

class _InheritedModel<T extends Model> extends InheritedWidget {
  final T model;
  final int version;

  _InheritedModel({Key key, Widget child, T model})
      : this.model = model,
        this.version = model._version,
        super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
      (oldWidget.version != version);
}
复制代码

看到这个类, 我们就可以发现作者的实现方式了, _InheritedModel 继承自 InheritedWidget , 上方 ScopedModel.build() 方法, 里面就返回了该 inherientWidget 对象, 但是与上方的例子还缺少子类对父类进行依赖的一步.

class ScopedModelDescendant<T extends Model> extends StatelessWidget

typedef Widget ScopedModelDescendantBuilder<T extends Model>(
  BuildContext context,
  Widget child,
  T model,
);

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
  final ScopedModelDescendantBuilder<T> builder;

  final Widget child;

  final bool rebuildOnChange;

  ScopedModelDescendant({
    @required this.builder,
    this.child,
    this.rebuildOnChange = true,
  });

  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      child,
      ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
    );
  }
}
复制代码

ScopedModelDescendant 就是 ScopedModel 的子孙, 那么可以看到它的 Build 方法, 里面有调用 ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange) , 我们把这个方法在拿出来看一下:

static T of<T extends Model>(
    BuildContext context, {
    bool rebuildOnChange = false,
  }) {
    final Type type = _type<_InheritedModel<T>>();

    Widget widget = rebuildOnChange
        ? context.inheritFromWidgetOfExactType(type)
        : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

    if (widget == null) {
      throw ScopedModelError();
    } else {
      return (widget as _InheritedModel<T>).model;
    }
  }
  
  static Type _type<T>() => T;
复制代码

到这里, 就可以发现它和 inherient 做法相同了, 首先获取到 InheritedWidget 的类型, _type<_InheritedModel<T>>() 得到 _InheritedModel<T> , 然后判断是否需要在改变的时候重绘, 默认是 True , 如果需要重绘就会调用 inheritFromWidgetOfExactType 去建立依赖, 如果为 false , 则会调用 ancestorInheritedElementForWidgetOfExactType , 这个方法不会建立依赖, 所以在改变的时候不会收到通知并重绘, 官方的注释有这么一句: This method does not establish a relationship with the target in the way that [inheritFromWidgetOfExactType] does.

最后贴一下作者提供的小例子

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

void main() {
  CounterModel model = CounterModel();
  runApp(MyApp(
    model: model,
  ));
}

class MyApp extends StatelessWidget {
  final CounterModel model;

  const MyApp({Key key, @required this.model}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // At the top level of our app, we'll, create a ScopedModel Widget. This
    // will provide the CounterModel to all children in the app that request it
    // using a ScopedModelDescendant.
    return ScopedModel<CounterModel>(
      model: model,
      child: MaterialApp(
        title: 'Scoped Model Demo',
        home: CounterHome('Scoped Model Demo'),
      ),
    );
  }
}

// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    // First, increment the counter
    _counter++;

    // Then notify all the listeners.
    notifyListeners();
  }
}

class CounterHome extends StatelessWidget {
  final String title;

  CounterHome(this.title);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            // Create a ScopedModelDescendant. This widget will get the
            // CounterModel from the nearest parent ScopedModel<CounterModel>.
            // It will hand that CounterModel to our builder method, and
            // rebuild any time the CounterModel changes (i.e. after we
            // `notifyListeners` in the Model).
            ScopedModelDescendant<CounterModel>(
              builder: (context, child, model) {
                return Text(
                  model.counter.toString(),
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
          ],
        ),
      ),
      // Use the ScopedModelDescendant again in order to use the increment
      // method from the CounterModel
      floatingActionButton: ScopedModelDescendant<CounterModel>(
        builder: (context, child, model) {
          return FloatingActionButton(
            onPressed: model.increment,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          );
        },
      ),
    );
  }
}

复制代码

这里也能验证我们上方所分析的, 首先需要构建 ScopedModel , 然后共享状态的子孙节点通过 ScopedModelDescendant 来添加.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

编译原理

编译原理

Alfred V.Aho、Jeffrey D.Ullman、Ravi Sethi / 李建中 / 机械工业出版社 / 2003-8 / 55.00元

《编译原理》作者Alfred V.Aho、Ravi Sethi和Jeffrey D.Ullman是世界著名的计算机 科学家,他们在计算机科学理论、数据库等很多领域都做出了杰出贡献。《编译原理》 是编译领域无可替代的经典著作,被广大计算机专业人士誉为“龙书”。《编译原理》一 直被世界各地的著名高等院校和科研机构(如贝尔实验室、哥伦比亚大学、普 林斯顿大学和斯坦福大学等)广泛用作本科生和研究生编译原理......一起来看看 《编译原理》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具