Flutter - Widget 分類對比

Widget 分類

如果按照是否是有狀態(tài)的分類方式,那么Widget就分為StatelessWidgetStatefulWidgetStatelessWidgetStatefulWidgetElement都是ComponentElement,并且都不具備RenderObject

他們UI的構(gòu)建都是調(diào)用build方法。區(qū)別就是StatelessWidget只是簡單的實(shí)現(xiàn)了ComponentElement,而StatefulWidget則復(fù)雜了許多,他的build是由_state去控制的,狀態(tài)和數(shù)據(jù)都保存在這里面, 這個(gè)在之前的文章中有提及。

StatelessElement代碼示例:

class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}

可以看出在更新的時(shí)候也只是把_dirty臟標(biāo)記設(shè)置為true,然后就重新構(gòu)建。

State和Element的生命周期對比

image-20210313144623647.png

State數(shù)據(jù)的傳遞

數(shù)據(jù)傳遞

上面的代碼,當(dāng)點(diǎn)擊FloatingActionButton之后,最終顯示在屏幕上的文字是什么?為什么?

答案:

上面的代碼當(dāng)我們點(diǎn)擊按鈕之后,內(nèi)容并不會(huì)發(fā)生改變,因?yàn)?code>StatePage的state已經(jīng)被創(chuàng)建過了,所以createState不會(huì)走兩次,故而data并不會(huì)發(fā)生改變(但是StatePagedata是發(fā)生了改變的),如果我們想使用更新之后的值,我們可以使用widte.data來引用。

class StatePage extends StatefulWidget {
  StatePage({this.data});

  final String data;

  @override
  _StatePageState createState() => _StatePageState();
}

class _StatePageState extends State<StatePage> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(widget.data ?? ""),
    );
  }
}

setState是如何實(shí)現(xiàn)刷新的?

setState內(nèi)部會(huì)調(diào)用_element.markNeedsBuild();方法markNeedsBuild方法會(huì)在內(nèi)部把_dirty設(shè)置為true,然后加入到定時(shí)器當(dāng)中,然后在下一幀的WidgetsBinding.drawFrame才會(huì)被繪制。此處也可以得知,setState并不會(huì)馬上生效。

RenderObject分類

RenderBox

特性:會(huì)根據(jù)parentconstraints大小判斷自己的布局方法,然后將constraints傳遞給child得到child的大小,最后根據(jù)child返回的Size決定自己的Size,如果沒有child,就使用自己的Size

他用于那些不涉及的滾動(dòng)的控件布局,他的兩個(gè)關(guān)鍵參數(shù)就是BoxConstraintsSize

RenderSliver

特性:因?yàn)槠渲饕糜赗enderViewport之后,里面涉及的運(yùn)算和屬性對比RenderBox要復(fù)雜上許多。他的兩個(gè)關(guān)鍵參數(shù)是SliverConstraints和SliverGeometry。

SliverConstraints和BoxConstraints對比,BoxContraints只包括了,最大/最小的高度/寬度。但是SliverConstraints則更多的是滑動(dòng)方向、滑動(dòng)偏移、滑動(dòng)容器大小、容器緩存大小和位置等相關(guān)參數(shù)。

Size和SliverGeometry進(jìn)行對比,Size只包括了寬和高。但是SliverGeometry包括了滑動(dòng)方位、繪制范圍、偏移等相關(guān)參數(shù)。

RenderBox和RenderSliver對比

RenderBox輸入輸出相較于RenderSliver更為簡單,RenderSliver更為關(guān)注滑動(dòng)、方向、緩存等關(guān)鍵點(diǎn),這也是因?yàn)槠湫枰蚔iewPort配合展示。例如我們經(jīng)常使用的ListView、GirdView、ScrollView等都是有Sliver和ViewPort組成的,可滑動(dòng)的區(qū)域內(nèi)不可以直接使用RenderBox,如果一定要使用必須用RenderSliver進(jìn)行嵌套后進(jìn)行布局。

對比

ViewPort

ViewPort根據(jù)自己的窗口的大小和偏移量,對child進(jìn)行布局計(jì)算,通過對child輸入SliverConstraints來得到childSliverGeometry,從而確定layoutpaint等相關(guān)信息。

RenderSliver對應(yīng)的Sliver控件需要在ViewPort中使用。

image-20210316101636712.png

當(dāng)外部的滑動(dòng)事件產(chǎn)生時(shí),就會(huì)觸發(fā)到ViewPortmarkNeedsLayout方法,之后變化重新進(jìn)行布局和繪制,并讓SliverViewPort中進(jìn)行偏移,達(dá)到看起來像是滑動(dòng)了的效果。

RenderViewPort中為了避免性能消耗,對于滑動(dòng)的時(shí)候內(nèi)部就會(huì)嘗試重新布局做了一個(gè)限制,最大的嘗試次數(shù)不能超過10次

ListViewGridView內(nèi)部都是一個(gè)SliverList構(gòu)成,他們的children布局也是通過SliverList進(jìn)行布局的。

RenderSliverList中,會(huì)通過傳入的ramainingCacheExtentscrollOffset等參數(shù)去決定哪些child需要布局顯示,哪些child不需要被布局繪制,從而保證了列表中內(nèi)存優(yōu)化和良好的繪制性能。

單元素與多元素分類

根據(jù)Widgetchild是否支持單個(gè)/多個(gè)child又可以分為SingleChildRenderObjectWidgetMultiChildRenderObjectWidget

像我們經(jīng)常使用的ClipOpacityPaddingAlignSizededBox等都屬于SingleChildRenderObjectWidget;而StackRowColumnRichText等則屬于MultiChildRenderObjectWidget。針對兩個(gè)不同的RenderObjectWidgetFlutter提供了CustomSingleChildLayoutCustomMultiChildLayout的抽象封裝。

SingleChildRenderObjectWidget

SingleChildRenderObjectWidget繼承RenderObjectWidget,因?yàn)橹挥幸粋€(gè)child,所以實(shí)現(xiàn)起來相對簡單。繪制流程是通過RenderObject計(jì)算出自身的最大、最小寬高,并且通過performLayout綜合得到child返回的Size、最后在進(jìn)行繪制。

MultiChildRenderObjectWidget

MultiChildRenderObjectWidget

從上圖可以看出相較于SingleChildRenderObjectWidgetMultiChildRenderObjectWidget實(shí)現(xiàn)起來要復(fù)雜許多,主要復(fù)雜的部分在于RenderBox,我們需要自定義一個(gè)類繼承于RenderBox,同時(shí)還得混入ContainerRenderObjectMixinRenderBoxContainerDefaultsMixin,然后去重寫他的兩個(gè)方法:setupParentDataperformLayout,然后在重寫paint方法,調(diào)用系統(tǒng)繪制方法,完成繪制操作。

下面用一個(gè)實(shí)際例子來演示:

01- 創(chuàng)建ContainerBoxparentData

這個(gè)就是對應(yīng)上圖中右下方的抽象類(ConstainerBoxParentData)的具體實(shí)現(xiàn)

class RenderCloudParentData extends ContainerBoxParentData<RenderBox> {
  /// 定義寬高
  double width;
  double height;

  /// 通過offset和width、height得到一個(gè)矩形區(qū)域
  Rect get content => Rect.fromLTWH(
        offset.dx,
        offset.dy,
        width,
        height,
      );
}
02-創(chuàng)建RenderBox

這個(gè)就是對應(yīng)上圖的RenderBox的具體實(shí)現(xiàn)

/// 從類的定義就可以很好的看出,該類需要繼承于RenderBox,
/// 同時(shí)還需要混入ContainerRenderObjectMixin、RenderBoxContainerDefaultsMixin
class RenderCloudWidget extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {

  /// 構(gòu)造方法
  /// * children
  /// * overflow 裁剪方式
  /// * ratio 比例
  RenderCloudWidget({
    List<RenderBox> children,
    Clip overflow = Clip.none,
    double ratio,
  })  : _ratio = ratio,
        _overflow = overflow {
    /// 這個(gè)是ContainerRenderObjectMixin的內(nèi)部方法,其內(nèi)部是一個(gè)雙線鏈表的結(jié)果,
    /// 主要是用于快速定位下一個(gè)、上一個(gè)renderObject
    addAll(children);
  }

  ///圓周
  double _mathPi = math.pi * 2;

  ///比例
  double _ratio;

  double get ratio => _ratio;

  set ratio(double value) {
    assert(value != null);
    if (_ratio != value) {
      _ratio = value;
      markNeedsPaint();
    }
  }

  /// 裁剪方式
  Clip get overflow => _overflow;

  set overflow(Clip value) {
    assert(value != null);
    if (_overflow != value) {
      _overflow = value;
      markNeedsPaint();
    }
  }

  Clip _overflow;

  /// 是否需要裁剪
  bool _needClip = false;

  /// 用于判斷是否重復(fù)區(qū)域了
  bool overlaps(RenderCloudParentData data) {
    Rect rect = data.content;

    RenderBox child = data.previousSibling;

    if (child == null) {
      return false;
    }

    do {
      RenderCloudParentData childParentData = child.parentData;
      if (rect.overlaps(childParentData.content)) {
        return true;
      }
      child = childParentData.previousSibling;
    } while (child != null);
    return false;
  }

  /// 這個(gè)就是需要重寫RenderBox其中的一個(gè)方法
  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! RenderCloudParentData) {
      child.parentData = RenderCloudParentData();
    }
  }

  /// 內(nèi)部布局方法,布局每一個(gè)child的位置大小
  @override
  void performLayout() {
    ///默認(rèn)不需要裁剪
    _needClip = false;

    ///沒有 childCount 不玩
    if (childCount == 0) {
      size = constraints.smallest;
      return;
    }

    ///初始化區(qū)域
    var recordRect = Rect.zero;
    var previousChildRect = Rect.zero;

    RenderBox child = firstChild;

    while (child != null) {
      var curIndex = -1;

      ///提出數(shù)據(jù)
      final RenderCloudParentData childParentData = child.parentData;

      child.layout(constraints, parentUsesSize: true);

      var childSize = child.size;

      ///記錄大小
      childParentData.width = childSize.width;
      childParentData.height = childSize.height;

      do {
        ///設(shè)置 xy 軸的比例
        var rX = ratio >= 1 ? ratio : 1.0;
        var rY = ratio <= 1 ? ratio : 1.0;

        ///調(diào)整位置
        var step = 0.02 * _mathPi;
        var rotation = 0.0;
        var angle = curIndex * step;
        var angleRadius = 5 + 5 * angle;
        var x = rX * angleRadius * math.cos(angle + rotation);
        var y = rY * angleRadius * math.sin(angle + rotation);
        var position = Offset(x, y);

        ///計(jì)算得到絕對偏移
        var childOffset = position - Alignment.center.alongSize(childSize);

        ++curIndex;

        ///設(shè)置為遏制
        childParentData.offset = childOffset;

        ///判處是否交疊
      } while (overlaps(childParentData));

      ///記錄區(qū)域
      previousChildRect = childParentData.content;
      recordRect = recordRect.expandToInclude(previousChildRect);

      ///下一個(gè)
      child = childParentData.nextSibling;
    }

    ///調(diào)整布局大小
    size = constraints
        .tighten(
      height: recordRect.height,
      width: recordRect.width,
    )
        .smallest;

    ///居中
    var contentCenter = size.center(Offset.zero);
    var recordRectCenter = recordRect.center;
    var transCenter = contentCenter - recordRectCenter;
    child = firstChild;
    while (child != null) {
      final RenderCloudParentData childParentData = child.parentData;
      childParentData.offset += transCenter;
      child = childParentData.nextSibling;
    }

    ///超過了嘛?
    _needClip =
        size.width < recordRect.width || size.height < recordRect.height;
  }

  /// 設(shè)置繪制默認(rèn)
  @override
  void paint(PaintingContext context, Offset offset) {
    if (!_needClip || _overflow == Clip.none) {
      defaultPaint(context, offset);
    } else {
      context.pushClipRect(
        needsCompositing,
        offset,
        Offset.zero & size,
        defaultPaint,
      );
    }
  }
  

  /// 觸摸測試,如果不想響應(yīng)就返回false,反正則是true
  @override
  bool hitTestChildren(HitTestResult result, {Offset position}) {
    return defaultHitTestChildren(result, position: position);
  }
}
03-創(chuàng)建Widget

主要是把RenderObjectWidget進(jìn)行關(guān)聯(lián)起來

/// 創(chuàng)建Widget,繼承與MultiChildRenderObjectWidget
/// 主要是和之前的RenderBox關(guān)聯(lián)起來
class CloudWidget extends MultiChildRenderObjectWidget {
  /// 自定義的相關(guān)屬性
  final Clip overflow;
  final double ratio;

  /// 構(gòu)造方法
  CloudWidget({
    Key key,
    this.ratio = -1,
    this.overflow = Clip.none,
    List<Widget> children = const <Widget>[],
  }) : super(key: key, children: children);

  /// 重寫創(chuàng)建RenderObject的方法,把之前創(chuàng)建的RenderCouldWidget返回
  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCloudWidget(ratio: ratio, overflow: overflow);
  }

  /// 在這里更新RenderCloudWidget的兩個(gè)關(guān)鍵參數(shù)
  @override
  void updateRenderObject(
      BuildContext context, covariant RenderCloudWidget renderObject) {
    /// ..表示級聯(lián)操作符
    renderObject
      ..ratio = ratio
      ..overflow = overflow;
  }
}
04-demo
///云詞圖
class CloudDemoPage extends StatefulWidget {
  @override
  _CloudDemoPageState createState() => _CloudDemoPageState();
}

class _CloudDemoPageState extends State<CloudDemoPage> {

  ///Item數(shù)據(jù)
  List<CloudItemData> dataList = const <CloudItemData>[
    CloudItemData('CloudGSY11111', Colors.amberAccent, 10, false),
    CloudItemData('CloudGSY3333333T', Colors.limeAccent, 16, false),
    CloudItemData('CloudGSYXXXXXXX', Colors.black, 14, true),
    CloudItemData('CloudGSY55', Colors.black87, 33, false),
    CloudItemData('CloudGSYAA', Colors.blueAccent, 15, false),
    CloudItemData('CloudGSY44', Colors.indigoAccent, 16, false),
    CloudItemData('CloudGSYBWWWWWW', Colors.deepOrange, 12, true),
    CloudItemData('CloudGSY<<<', Colors.blue, 20, true),
    CloudItemData('FFFFFFFFFFFFFF', Colors.blue, 12, false),
    CloudItemData('BBBBBBBBBBB', Colors.deepPurpleAccent, 14, false),
    CloudItemData('CloudGSY%%%%', Colors.orange, 20, true),
    CloudItemData('CloudGSY%%%%%%%', Colors.blue, 12, false),
    CloudItemData('CloudGSY&&&&', Colors.indigoAccent, 10, false),
    CloudItemData('CloudGSYCCCC', Colors.yellow, 14, true),
    CloudItemData('CloudGSY****', Colors.blueAccent, 13, false),
    CloudItemData('CloudGSYRRRR', Colors.redAccent, 12, true),
    CloudItemData('CloudGSYFFFFF', Colors.blue, 12, false),
    CloudItemData('CloudGSYBBBBBBB', Colors.cyanAccent, 15, false),
    CloudItemData('CloudGSY222222', Colors.blue, 16, false),
    CloudItemData('CloudGSY1111111111111111', Colors.tealAccent, 19, false),
    CloudItemData('CloudGSY####', Colors.black54, 12, false),
    CloudItemData('CloudGSYFDWE', Colors.purpleAccent, 14, true),
    CloudItemData('CloudGSY22222', Colors.indigoAccent, 19, false),
    CloudItemData('CloudGSY44444', Colors.yellowAccent, 18, true),
    CloudItemData('CloudGSY33333', Colors.lightBlueAccent, 17, false),
    CloudItemData('CloudGSYXXXXXXXX', Colors.blue, 16, true),
    CloudItemData('CloudGSYFFFFFFFF', Colors.black26, 14, false),
    CloudItemData('CloudGSYZUuzzuuu', Colors.blue, 16, true),
    CloudItemData('CloudGSYVVVVVVVVV', Colors.orange, 12, false),
    CloudItemData('CloudGSY222223', Colors.black26, 13, true),
    CloudItemData('CloudGSYGFD', Colors.yellow, 14, true),
    CloudItemData('GGGGGGGGGG', Colors.deepPurpleAccent, 14, false),
    CloudItemData('CloudGSYFFFFFF', Colors.blueAccent, 10, true),
    CloudItemData('CloudGSY222', Colors.limeAccent, 12, false),
    CloudItemData('CloudGSY6666', Colors.blue, 20, true),
    CloudItemData('CloudGSY33333', Colors.teal, 14, false),
    CloudItemData('YYYYYYYYYYYYYY', Colors.deepPurpleAccent, 14, false),
    CloudItemData('CloudGSY  3  ', Colors.blue, 10, false),
    CloudItemData('CloudGSYYYYYY', Colors.black54, 17, true),
    CloudItemData('CloudGSYCC', Colors.lightBlueAccent, 11, false),
    CloudItemData('CloudGSYGGGGG', Colors.deepPurpleAccent, 10, false)
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: new Text("CloudDemoPage"),
      ),
      body: new Center(
        child: Container(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.width,

          ///利用 FittedBox 約束 child
          child: new FittedBox(
            /// Cloud 布局
            child: Container(
              padding: EdgeInsets.symmetric(vertical: 10, horizontal: 6),
              color: Colors.brown,

              ///布局
              child: CloudWidget(
                ///容器寬高比例
                ratio: 1,
                children: <Widget>[
                  for (var item in dataList)

                  ///判斷是否旋轉(zhuǎn)
                    RotatedBox(
                      quarterTurns: item.rotate ? 1 : 0,
                      child: Text(
                        item.text,
                        style: new TextStyle(
                          fontSize: item.size,
                          color: item.color,
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class CloudItemData {
  ///文本
  final String text;

  ///顏色
  final Color color;

  ///旋轉(zhuǎn)
  final bool rotate;

  ///大小
  final double size;

  const CloudItemData(
      this.text,
      this.color,
      this.size,
      this.rotate,
      );
}

CustomMultiChildLayout

官方為了簡化我們實(shí)現(xiàn)自定義布局的方式,還提供了CustomMultiChildLayout這樣的類,這個(gè)類也是繼承了MultiChildRenderObjectWidget,并通過一個(gè)代理(MultiChildLayoutDelegate)來完成自定義UI相關(guān)的功能,通過這個(gè)代理,我們可以直接去重寫內(nèi)部的performLayout方法,從而達(dá)到我們自定布局的效果。

01-創(chuàng)建Delegate
class CircleLayoutDelegate extends MultiChildLayoutDelegate {
  final List<String> customLayoutId;
  final Offset center;

  Size childSize;

  CircleLayoutDelegate(
    this.customLayoutId, {
    this.center = Offset.zero,
    this.childSize,
  });

  @override
  void performLayout(Size size) {
    for (var item in customLayoutId) {
      if (hasChild(item)) {
        double r = 100;

        /// 下標(biāo)
        int index = int.parse(item);

        /// 均分
        double step = 360 / customLayoutId.length;

        /// 角度
        double hd = (2 * math.pi / 360) * step * index;

        var x = center.dx + math.sin(hd) * r;
        var y = center.dy + math.cos(hd) * r;

        /// 使用??= 避免多次賦值
        childSize ??= Size(size.width / customLayoutId.length,
            size.height / customLayoutId.length);

        layoutChild(item, BoxConstraints.loose(childSize));

        final double centerX = childSize.width * 0.5;
        final double centerY = childSize.height * 0.5;

        var result = Offset(x - centerX, y - centerY);

        /// 設(shè)置child位置
        positionChild(item, result);
      }
    }
  }

  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
    return true;
  }
}
02-使用

class CustomMultiLayoutPage extends StatefulWidget {
  @override
  _CustomMultiLayoutPageState createState() => _CustomMultiLayoutPageState();
}

class _CustomMultiLayoutPageState extends State<CustomMultiLayoutPage> {
  ///用于 LayoutId 指定
  ///CircleLayoutDelegate 操作具體 Child 的 ChildId 是通過 LayoutId 指定的
  List customLayoutId = ["0", "1", "2", "3", "4"].toList();

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final childSize = 66.0;
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Container(
          color: Colors.yellowAccent,
          width: size.width,
          height: size.width,
          child: CustomMultiChildLayout(
            delegate: CircleLayoutDelegate(
                customLayoutId,
                childSize: Size(childSize, childSize),
              center: Offset(size.width * 0.5, size.width * 0.5),
            ),
            children: [
              ///使用 LayoutId 指定 childId
              for (var item in customLayoutId)
                new LayoutId(id: item, child: ContentItem(item, childSize)),
            ],
          ),
        ),
      ),
      persistentFooterButtons: <Widget>[
        TextButton(onPressed: () {
            setState(() {
              customLayoutId.add("${customLayoutId.length}");
            });
          },
          child: Icon(Icons.add),
        ),
        TextButton(onPressed: () {
            setState(() {
              if (customLayoutId.length > 1) {
                customLayoutId.removeLast();
              }
            });
          },
          child: Icon(Icons.remove),
        ),
      ],
    );
  }
}

class ContentItem extends StatelessWidget {
  final String text;

  final double childSize;

  ContentItem(this.text, this.childSize);

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Theme.of(context).primaryColor,
      borderRadius: BorderRadius.circular(childSize / 2.0),
      child: InkWell(
        radius: childSize / 2.0,
        customBorder: CircleBorder(),
        onTap: () {},
        child: Container(
          width: childSize,
          height: childSize,
          child: Center(
            child: Text(
              text,
              style: Theme.of(context)
                  .textTheme
                  .headline6
                  .copyWith(color: Colors.white),
            ),
          ),
        ),
      ),
    );
  }
}
效果圖
111.gif

InheritedWidget共享狀態(tài)

InheritedWidgeFlutter Widget中非常重要的一個(gè)構(gòu)成部分,因?yàn)?code>InheritedWidget常被用于數(shù)據(jù)共享。比如使用頻率很高的:Theme/ThemeDataText/DefaultTextStyleSlider/SliderThemeIcon/IconTheme等內(nèi)部都是通過InheritedWidget實(shí)現(xiàn)數(shù)據(jù)共享的。并且Flutter中部分的狀態(tài)管理框架,內(nèi)部的狀態(tài)共享方法也是基于InheritedWidget去實(shí)現(xiàn)的。

InheritedWidget繼承自ProxyWidget,本身并不具備繪制的能力,但共享這個(gè)Widget等與共享Widget內(nèi)保存的數(shù)據(jù),獲取Widget就可以獲取到其內(nèi)部保存的數(shù)據(jù),如下圖:


image-20210317145532940.png

每一個(gè)Element當(dāng)中都有一個(gè)成員變量:Map<Type, InheritedElement> _inheritedWidgets,改成員變量默認(rèn)是空,之后當(dāng)父控件是InheritedWidget或者本身是InheritedWidget的時(shí)候才會(huì)初始化,當(dāng)父控件是InheritedWidget的時(shí)候,這個(gè)Map會(huì)逐級向下傳遞于合并。

那么context.inheritedFromWidgetOfExactType內(nèi)部做了啥呢?

通過查看Element的源碼截圖部分片段

  @override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    /// 首先判斷是否有inheritedElement類型的數(shù)據(jù)
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    
    /// 找到了
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      
      /// 添加到依賴集合中,并且通過updateDependencies將當(dāng)前的Element添加到_dependencies Map中,并且返回InheritedWidget
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }


/// 下面兩個(gè)方法就是添加過程的實(shí)現(xiàn)
  @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    return dependOnInheritedElement(ancestor, aspect: aspect);
  }

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    /// 創(chuàng)建_dependencies
    _dependencies ??= HashSet<InheritedElement>();
    /// 添加InheritedElement到集合中
    _dependencies.add(ancestor);
    /// 跟新依賴
    ancestor.updateDependencies(this, aspect);
    /// 返回InheritedWidget
    return ancestor.widget;
  }
InheritedWidget是如何通知StatefulWidget進(jìn)行更新的?

例如:當(dāng)我們在外界調(diào)用Theme.of(context)的時(shí)候,BuildContext的實(shí)現(xiàn)就是Element,所以當(dāng)內(nèi)部調(diào)用到context.inheritedFromWidgetOfExactType時(shí),就會(huì)將context所代表的Element添加到InheritedElement_dependents中,當(dāng)InheritedElement被更新的時(shí)候,就會(huì)觸發(fā)到齊內(nèi)部的notifyClients方法,該方法就會(huì)挨個(gè)遍歷被加入到_dependents,從而觸發(fā)到didChangeDependcies,然后就會(huì)更新UI

ErrorWidget 異常處理

在以往的開發(fā)中,當(dāng)我們程序拋出一些未處理的異常或者錯(cuò)誤的時(shí)候,就會(huì)引發(fā)程序的crash,但是在Flutter中則不會(huì),這是因?yàn)?code>Flutter中有一個(gè)全局處理的地方;

當(dāng)我們的代碼發(fā)生一些問題之后,在debug模式下可能會(huì)有某些或者整個(gè)頁面變成紅色,并顯示一些錯(cuò)誤信息;在release模式下,則會(huì)顯示灰色的并沒有錯(cuò)誤提示。

為了能讓我們的產(chǎn)品體驗(yàn)更好,我們可以在main方法中做一些處理,讓錯(cuò)誤看起來更加優(yōu)雅

void main() {
  runZoned((){
    ErrorWidget.builder = (FlutterErrorDetails details) {
      Zone.current.handleUncaughtError(details.exception, details.stack);
      return Container(color: Colors.orange,);
    };

    FlutterError.onError = (FlutterErrorDetails details) async {
      FlutterError.dumpErrorToConsole(details);
      Zone.current.handleUncaughtError(details.exception, details.stack);
    };

    runApp(MyApp());
  }, onError: (Object obj, StackTrace stack) {
    print(obj);
    print(stack);
  });
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容