scoped_model 源码阅读

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

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

我们先来看一下 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 中, 每一个实例都有这个属性. 它的作用是存储上级节点 WidgetElement 之间的映射.

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 来添加.


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

查看所有标签

猜你喜欢:

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

腾讯网UED体验设计之旅

腾讯网UED体验设计之旅

任婕 等 / 电子工业出版社 / 2015-4 / 99.00元

《腾讯网UED体验设计之旅》是腾讯网UED的十年精华输出,涵盖了丰富的案例、极富冲击力的图片,以及来自腾讯网的一手经验,通过还原一系列真实案例的幕后设计故事,从用户研究、创意剖析、绘制方法、项目管理等实体案例出发,带领读者经历一场体验设计之旅。、 全书核心内容涉及网媒用户分析与研究方法、门户网站未来体验设计、H5技术在移动端打开的触控世界、手绘原创设计、改版迭代方法、文字及信息图形化设计、媒......一起来看看 《腾讯网UED体验设计之旅》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具