scoped_model 源碼閱讀

最近作的項目有一個需求, 就是有不一樣的主題, 那麼在設置頁面就應該讓生效, 這就是全局狀態的一個管理了, Flutter 系統提供了 InheritedWidget, 可是這裏咱們來使用 scoped_model(基於InheritedWidget進行了封裝).markdown

InheritedWidget

咱們先來看一下InheritedWidget是如何實現共享數據的.app

如下是官網上的一個小例子less

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),
            )
          ],
        ),
      ),
    );
  }
}
複製代碼

這裏須要注意:ide

  • context.inheritFromWidgetOfExactType(ShareDataWidget)來獲取到指定InheritedWidget中的數據, 實際通知的時候是父Widget往下傳遞仍是子Widget往上遍歷呢?

接下lai 探索緣由函數

InheritedWidget的源碼很簡單, 繼承了ProxyWidget, 也沒有實現太多邏輯, 對createElement進行了實現, 還定義了updateShouldNotify, 該方法的意思就是更新的時候是否應該通知在 build 階段經過inheritFromWidgetOfExactType查找該 Widget 的子 Widget.ui

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方法.this

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屬性保存到自身(這樣就保證了父級的向子集傳遞特性).spa

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
複製代碼

可是這個_inheritedWidgets屬性又是在哪裏出現呢? 它定義在 Element中, 每個實例都有這個屬性. 它的做用是存儲上級節點WidgetElement之間的映射.debug

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的實現.rest

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

相關文章
相關標籤/搜索