Flutter中Widget的生命周期和渲染原理

Flutter开发实战之Widget体系与布局原理 Flutter的Widget体系与布局原理 阅读详情

widget

Flutter中Widget的生命周期

  • StatelessWidget是通过构造函数(Constructor)接收父Widget直接传入值,然后调用build方法来构建,整个过程非常简单
  • 而StatefulWidget需要通过State来管理其数据,并且还要监控状态的改变决定是否重新build整个Widget
  • 这里主要讨论StatefulWidget的生命周期,就是它从创建到显示再到更新最后到销毁的整个过程
  • StatefulWidget本身由两个类组成的:StatefulWidget和State
  • 在StatefulWidget中的相关方法主要就是
    • 执行StatefulWidget的构造函数(Constructor)来创建出StatefulWidget
    • 执行StatefulWidget的createState方法,来创建一个维护StatefulWidget的State对象
    • 所以我们探讨StatefulWidget的生命周期, 最终是探讨State的生命周期
  • 那么为什么Flutter在设计的时候, StatefulWidget的build方法要放在State中而不是自身呢
    • 首先build出来的Widget是需要依赖State中的变量(数据/自定义的状态)的
    • Flutter在运行过程中, Widget是不断的创建和销毁的, 当我们自己的状态改变时, 我们只希望刷新当前Widget, 并不希望创建新的State

图片来源网络

上面图片大概列出了StatefulWidget的简单的函数调用过程

constructor

调用createState创建State对象时, 执行State类的构造方法(Constructor)来创建State对象

initState

  • initState是StatefulWidget创建完后调用的第一个方法,而且只执行一次
  • 类似于iOS的viewDidLoad,所以在这里View并没有完成渲染
  • 我们可以在这个方法中执行一些数据初始化的操作,或者发送网络请求
  @override
  void initState() {
    // 这里必须调用super的方法
    super.initState();
    print('4. 调用_HomeScreenState----initState');
  }
  • 这个方法是重写父类的方法,必须调用super,因为父类中会进行一些其他操作
  • 另一点在源码中, 会看到这个方法中有一个mustCallSuper的注解, 这里就限制了必须调用父类的方法
  @protected
  @mustCallSuper
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }

didChangeDependencies

  • didChangeDependencies在整个过程中可能会被调用多次, 但是也只有下面两种情况下会被调用
  1. 在StatefulWidget第一次创建的时候didChangeDependencies会被调用一次, 会在initState方法之后会被立即调用
  2. 从其他对象中依赖一些数据发生改变时, 比如所依赖的InheritedWidget状态发生改变时, 也会被调用

build

  • build同样也会被调用多次
  • 在上述didChangeDependencies方法被调用之后, 会重新调用build方法, 来看一下我们当前需要重新渲染哪些Widget
  • 当每次所依赖的状态发生改变的时候build就会被调用, 所以一般不要将比较好使的操作放在build方法中执行

didUpdateWidget

执行didUpdateWidget方法是在当父Widget触发重建时,系统会调用didUpdateWidget方法

dispose

  • 当前的Widget不再使用时,会调用dispose进行销毁
  • 这时候就可以在dispose里做一些取消监听、动画的操作
  • 到这里, 也就意味着整个生命周期的过程也就结束了

setState

  • setState方法可以修改在State中定义的变量
  • 当我们手动调用setState方法,会根据最新的状态(数据)来重新调用build方法,构建对应的Widgets
  • setState内部其实是通过调用_element.markNeedsBuild();实现更新Widget

整个过程的代码如下:

class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 调用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    print('2. 调用的HomeScreen---createState');
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {

  int _counter = 0;

  _HomeScreenState() {
    print('3. 调用_HomeScreenState----constructor');
  }

  @override
  void initState() {
    // 这里必须调用super的方法
    super.initState();
    print('4. 调用_HomeScreenState----initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('调用_HomeScreenState----didChangeDependencies');
  }
  
  @override
  Widget build(BuildContext context) {
    print('5. 调用_HomeScreenState----build');
    return Scaffold(
      appBar: AppBar(title: Text('生命周期', style: TextStyle(fontSize: 20))),
      body: Center(
        child: Column(
          children: <Widget>[
            Text('当前计数: $_counter', style: TextStyle(fontSize: 20),),
            RaisedButton(
              child: Text('点击增加计数', style: TextStyle(fontSize: 20),),
              onPressed: () {
                setState(() {
                  _counter++;
                });
              }
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();

    print('6. 调用_HomeScreenState---dispose');
  }
}

打印结果如下:

flutter: 1. 调用HomeScreen---constructor
flutter: 2. 调用的HomeScreen---createState
flutter: 3. 调用_HomeScreenState----constructor
flutter: 4. 调用_HomeScreenState----initState
flutter: 调用_HomeScreenState----didChangeDependencies
flutter: 5. 调用_HomeScreenState----build

// 每次调用setState, 都会执行build
flutter: 5. 调用_HomeScreenState----build
flutter: 5. 调用_HomeScreenState----build

Flutter渲染原理

在Flutter中渲染过程是通过Widget, Element和RenderObject实现的, 下面是FLutter中的三种树结构

Flutter

Widget

这是Flutter官网对Widget的说明

Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

  • Flutter的Widgets的灵感来自React,中心思想是使用这些Widgets来搭建自己的UI界面
  • 通过当前Widgets的配置和状态描述这个页面应该展示成什么样子
  • 当一个Widget发生改变时,Widget就会重新build它的描述,框架会和之前的描述进行对比,来决定使用最小的改变在渲染树中,从一个状态到另一个状态
  • 从这段说明中大概意思也就是
    • Widgets只是页面描述层面的, 并不涉及渲染层面的东西, 而且如果所依赖的配置和状态发生变化的时候, 该Widgets会重新build
    • 而对于渲染对象来说, 只会使用最小的开销重新渲染发生改变的部分而不是全部重新渲染
  • Widget Tree树结构
    • 在整个Flutter项目结构也是由很多个Widget构成的, 本质上就是一个Widget Tree
    • 在上面的类似Widget Tree结构中, 很可能会有大量的Widget在树结构中存在引用关系, 而且每个Widget所依赖的配置和状态发生改变的时候, Widget都会重新build, Widget会被不断的销毁和重建,那么意味着这棵树非常不稳定
    • 所以Flutter Engin也不可能直接把Widget渲染到界面上, 这事极其损耗性能的, 所以在渲染层面Flutter引用了另外一个树结构RenderObject Tree

RenderObject

下面是Flutter官网对RenderObject的说明

An object in the render tree.

The RenderObject class hierarchy is the core of the rendering library’s reason for being.

RenderObjects have a parent, and have a slot called parentData in which the parent RenderObject can store child-specific data, for example, the child position. The RenderObject class also implements the basic layout and paint protocols.

  • 每一个RenderObject都是渲染树上的一个对象
  • RenderObject层是渲染库的核心, 最终Flutter Engin是把RenderObject真正渲染到界面上的
  • RenderObject Tree
    • 在渲染过程中, 最终都会把Widget转成RenderObject, Flutter最后在解析的时候解析的也是我们的RenderObject Tree, 但是并不是每一个Widget都会有一个与之对应的RenderObject
    • 因为很多的Widget都不是壳渲染的Widget, 而是类似于一个盒子的东西, 对其他Widget进行包装的作用

Element

下面是Flutter官网对Element的说明

An instantiation of a Widget at a particular location in the tree.

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

  • Element是Widget在树中具有特定位置的是实例化
  • Widget描述如何配置子树和当前页面的展示样式, 每一个Element代表了在Element Tree中的特定位置
  • 如果Widget所依赖的配置和状态发生改变的时候, 和Element关联的Widget是会发生改变的, 但是Element的特定位置是不会发生改变的
  • Element Tree中的每一个Element是和Widget Tree中的每一个Widget一一对应的
    • Element Tree类似于HTML中的虚拟DOM, 用于判断和决定哪些RenderObject是需要更新的
    • 当Widget Tree所依赖的状态发生改变(更新或者重新创建Widget)的时候, Element根据拿到之前所保存的旧的Widget和新的Widget做一个对比, 判断两者的Key和类型是否是相同的, 相同的就不需要重新创建, 有需要的话, 只需要更新对应的属性即可

对象的创建过程

Widget

  • 在Flutter中Widget有可渲染的和不可渲染的(组件Widget)
    • 组件Widget: 类似Container…等等
    • 可渲染Widget: 类似Padding…等等
  • 下面我们先看一下组件Widget(Container)的实现过程和继承关系
// 继承关系Container --> StatelessWidget --> Widget
class Container extends StatelessWidget {
  @override
  Widget build(BuildContext context) {}
}

// 抽象类
abstract class StatelessWidget extends Widget {
  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
}
  • 从上面的代码可以看到, 继承关系比较简单, 并没有创建RenderObject对象
  • 我们经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在build方法中组装起来,并不是一个真正可以渲染的Widget

RenderObject

这里来看一下可渲染Widget的继承关系和相关源代码, 这里以Padding为例

// 继承关系: Padding --> SingleChildRenderObjectWidget --> RenderObjectWidget --> Widget
class Padding extends SingleChildRenderObjectWidget {
  @override
  RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderPadding renderObject) {
    renderObject
      ..padding = padding
      ..textDirection = Directionality.of(context);
  }
}

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

abstract class RenderObjectWidget extends Widget {
  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
  • 在Padding的类中,我们找不到任何和渲染相关的代码,这是因为Padding仅仅作为一个配置信息,这个配置信息会随着我们设置的属性不同,频繁的销毁和创建
  • 所以真正的渲染相关的代码那就只能在RenderObject里面了
  • 上面代码中, 在Padding类里面有一个核心方法createRenderObject是用于创建一个RenderObject的
  • 而且方法createRenderObject是来源于RenderObjectWidget这个抽象类里面的一个抽象方法
  • 抽象方法是必须被子类实现的,但是它的子类SingleChildRenderObjectWidget也是一个抽象类,所以可以不实现父类的抽象方法
  • 但是Padding不是一个抽象类,必须在这里实现对应的抽象方法,而它的实现就是下面的实现
// 这里目的是为了创建一个RenderPadding
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}

上面这段代码中, 最终是创建了一个RenderPadding, 而这个RenderPadding又是什么呢? 下面看看他的继承关系和相关源代码

// 继承关系: RenderPadding --> RenderShiftedBox --> RenderBox --> RenderObject
class RenderPadding extends RenderShiftedBox {}

abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {}

abstract class RenderBox extends RenderObject {}

RenderObject又是如何实现布局和渲染的呢

// 当外面修改padding时
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}

// RenderPadding类里面会调用padding属性的set方法
set padding(EdgeInsetsGeometry value) {
    if (_padding == value)
      // 如果传过来的值和之前的一样, 就不会被重新渲染, 直接return
      return;
    _padding = value;
    _markNeedResolution();
}

// 内部会调用markNeedsLayout
void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
}

// 这里是RenderObject里面的一些核心方法
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  // markNeedsLayout是RenderObject类里面的方法
  // markNeedsLayout的目的就是标记在下一帧绘制时,需要重新布局performLayout
  void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }

  // 
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
    }
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
  }
  
  // RenderObject还有一个可被子类重写的paint方法
  void paint(PaintingContext context, Offset offset) { }
}

Element

  • 在上面介绍Widget中提到过我们写的大量的Widget在树结构中存在引用关系,但是Widget会被不断的销毁和重建,那么意味着这棵树非常不稳定
  • 如果Widget所依赖的配置和状态发生改变的时候, 和Element关联的Widget是会发生改变的, 但是Element的特定位置是不会发生改变的
  • Element是Widget在树中具有特定位置的是实例化, 是维系整个Flutter应用程序的树形结构的稳定
  • 接下来看下Element是如何被创建和引用的, 这里还是以Container和Padding为例
// 在Container的父类StatelessWidget中, 实例化了其父类的一个抽象方法
// 继承关系: StatelessElement --> ComponentElement --> Element
abstract class StatelessWidget extends Widget {
  // 实例化父类的抽象方法, 并把当前Widget作为参数传入了(this)
  @override
  StatelessElement createElement() => StatelessElement(this);
}

// 在Padding的父类SingleChildRenderObjectWidget中, 实例化了其父类的一个抽象方法
// 继承关系: SingleChildRenderObjectElement --> RenderObjectElement --> Element
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  
  // 实例化父类的抽象方法, 并把当前Widget作为参数传入了(this)
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
  • 在每一次创建Widget的时候,会创建一个对应的Element,然后将该元素插入树中
  • 上面代码SingleChildRenderObjectWidget实例化了父类的抽象方法createElement创建一个Element, 并把当前Widget(this)作为SingleChildRenderObjectElement构造方法的参数传入
  • 这也就意味着创建出来的Element保存了对当前Widget的引用
  • 在创建完一个Element之后,Framework会调用mount方法来将Element插入到树中具体的位置
  • 这是在Element类中的mount方法, 这里主要的作用就是把自己做一个挂载操作
  /// Add this element to the tree in the given slot of the given parent.
  ///
  /// The framework calls this function when a newly created element is added to
  /// the tree for the first time. Use this method to initialize state that
  /// depends on having a parent. State that is independent of the parent can
  /// more easily be initialized in the constructor.
  ///
  /// This method transitions the element from the "initial" lifecycle state to
  /// the "active" lifecycle state.
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }

StatelessElement

Container创建出来的是StatelessElement, 下面我们探索一下StatelessElement创建完成后, framework调用mount方法的过程, 这里只留下了相关核心代码

abstract class ComponentElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    _firstBuild();
  }

  void _firstBuild() {
    rebuild();
  }
  
  @override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineWhitelistArguments);

    Widget built;
    try {
      // 这里调用的build方法, 当前类也没有实现, 所以还是只能到调用者(子类里面找该方法的实现)
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      
    } finally {
      _dirty = false;
    }
    try {
      _child = updateChild(_child, built, slot);
      
    } catch (e, stack) {
      
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

  @protected
  Widget build();
}


abstract class Element extends DiagnosticableTree implements BuildContext {
  // 构造方法, 接收一个widget参数
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  @override
  Widget get widget => _widget;
  Widget _widget;
  
  void rebuild() {
    if (!_active || !_dirty)
      return;
      
    Element debugPreviousBuildTarget;
    
    // 这里调用的performRebuild方法, 在当前类并没有实现, 只能去自己的类里面查找实现
    performRebuild();
  }

  /// Called by rebuild() after the appropriate checks have been made.
  @protected
  void performRebuild();
}


class StatelessElement extends ComponentElement {
  // 这里的widget就是之前StatelessWidget中调用createElement创建element时传过来的this(widget)
  StatelessElement(StatelessWidget widget) : super(widget);

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

  // 这里的build方法就是拿到当前的widget, 并且调用自己的build方法
  @override
  Widget build() => widget.build(this);
}

上面的代码看着有点乱, 下面就理一下

  1. 这里我们创建的是StatelessElement, 在创建完一个Element之后,Framework会调用mount方法
  2. 在ComponentElement类中重写了mount方法, 所以framwork会调用这里的mount方法
  3. 在mount方法中直接调用的_firstBuild方法(第一次构建)
  4. 在_firstBuild方法又是直接调用的rebuild方法(重新构建)
  5. 然而在ComponentElement类中没有重写rebuild方法, 所以还是要调用父类的rebuild方法
  6. 在rebuild方法会调用performRebuild方法, 而且是调用ComponentElement内重写的performRebuild方法
  7. 在performRebuild方法内, 会调用build方法, 并用Widget类型的build接收返回值
  8. 而这个build方法在StatelessElement中的实现如下
  9. 也就是说, 在创建Element之后, 创建出来的elment会拿到传过来的widget, 然后调用widget自己的build方法, 这也就是为什么所有的Widget创建出来之后都会调用build方法的原因
Widget build() => widget.build(this);

所以在StatelessElement调用mount烦恼歌发最主要的作用就是挂在之后调用_firstBuild方法, 最终通过widget调用对应widget的build方法构建更多的东西

RenderObjectElement

  • 下面看一下可渲染的Widget又是如何创建Element的, 这里还是以Padding为例
  • 之前有提到Padding是继承自SingleChildRenderObjectWidget的, 而createElement方法也是在这个类中被实现的
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  // 这里是创建了一个SingleChildRenderObjectElement对象
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
  • 上面的代码中Padding是通过父类创建了一个SingleChildRenderObjectElement对象
  • SingleChildRenderObjectElement是继承自RenderObjectElement
  • RenderObjectElement继承自Element
  • 接下来就是看一下mount方法的调用过程
/// 以下源码并不全, 这里只是拷贝了一些核心方法和相关源码
class SingleChildRenderObjectElement extends RenderObjectElement {
  // 同样构造函数接收一个widget参数
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

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

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
}


// RenderObjectElement类的相关实现
abstract class RenderObjectElement extends Element {
  // 构造函数接收一个widget参数
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  /// 创建一个RenderObject类型的变量
  @override
  RenderObject get renderObject => _renderObject;
  RenderObject _renderObject;


  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    // 在这里通过传过来的widget调用createRenderObject创建一个_renderObject
    _renderObject = widget.createRenderObject(this);
    _dirty = false;
  }
}
  • 从上面的代码看SingleChildRenderObjectElement类中的mount方法核心是调用父类(RenderObjectElement)的mount方法
  • 而RenderObjectElement中的mount方法, 主要就是通过widget调用它的createRenderObject方法创建一个renderObject
  • 所以对于RenderObjectElement来说, fromework调用mount方法, 其目的就是为了创建renderObject
  • 这也就意味着Element对_renderObject也会有一个引用
  • 也就是说Element不但对_widget有一个引用, 对_renderObject也会有一个引用

StatefulElement

  • 上面提到StatefulWidget是由两部分构成的StatefulWidget的State
  • 而StatefulWidget是通过createState方法,来创建一个维护StatefulWidget的State对象
class StatefulElement extends ComponentElement {
  /// 构造函数
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    
    _state._element = this;
    
    _state._widget = widget;
  }

  State<StatefulWidget> get state => _state;
  State<StatefulWidget> _state;
}
  • 在StatefulElement内定义了一个_state变量, 并且存在对_widget的引用
  • 而在StatefulElement的构造方法中, 直接通过参数widget调用其内部的createState方法, 这个是StatefulWidget中的一个抽象方法(子类必须实现), 相信这个方法都比较熟悉
class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 调用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Container()
  }
}
  • StatefulElement创建完成之后, fromework就会调用mount方法挂载, 这个过程就和上面StatelessElement中的mount方法的调用过程基本一样了
  • 两者不同的是:
    • StatelessElement中最后是通过widget调用widget.build(this)方法
    • StatefulElement中最后是通过_state调用_state.build(this)方法, 也就是上面_HomeScreenState的build方法
@override
Widget build() => _state.build(this);

BuildContext

上面多次提到的build方法是有参数的, 而且不管是StatelessWidget还是State, 他们build方法的参数都是BuildContext

// StatelessWidget
abstract class StatelessWidget extends Widget {
  @protected
  Widget build(BuildContext context);
}

// State
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  @protected
  Widget build(BuildContext context);
}

在ComponentElement创建完成之后, 会调用mount方法, 最终都会调用对应的build方法

class StatelessElement extends ComponentElement {
  @override
  Widget build() => widget.build(this);
}

class StatefulElement extends ComponentElement {
  @override
  Widget build() => _state.build(this);
}
  • 上面的build方法传入的参数都是Element, 所以本质上BuildContext就是当前的Element
  • BuildContext主要的作用就是知道我当前构建的这个Widget在这个Element Tree上面的位置信息, 之后就可以沿着这这个Tree喜爱那个上查找相关的信息
  • 下面是两者的继承关系
abstract class Element extends DiagnosticableTree implements BuildContext {}

小总结

StatelessElement
  • 在Widget创建出来之后, Flutter框架一定会根据这个Widget创建出一个对应的Element, 每一个Widget都有一个与之对应的Element
  • Element对对当前Widget产生一个引用_widget
  • element创建完成后, fromework会调用mount方法, 最终调用_widget.build(this)方法
StatefulElement
  • 在Widget创建出来之后, Flutter框架一定会根据这个Widget创建出一个对应的Element, 每一个Widget都有一个与之对应的Element
  • 在StatefulElement构造函数中会调用widget.createState()创建一个_state, 并引用_state
  • 并且会把widget赋值给_state的一个引用_widget: _state._widget = widget;, 这样在State类中就可以通过this.state拿到当前的Widget
  • element创建完成后, fromework会调用mount方法, 最终调用_state.build(this)方法
RenderObjectElement
  • 在Widget创建出来之后, Flutter框架一定会根据这个Widget创建出一个对应的Element, 每一个Widget都有一个与之对应的Element
  • element创建完成后, fromework会调用mount方法, 在mount方法中会通过widget调用widget.createRenderObject(this)创建一个renderObject, 并赋值给_renderObject
  • 所以创建的RenderObjectElement对象也会对RenderObject产生一个引用

Widget的key

我们之前创建的每一个Widget, 在其构造方法中我们都会看到一个参数Key, name这个Key到底有何作用又何时使用呢

const Scaffold({ Key key, ... })
const Container({ Key key, ... })
const Text({ Key key, ... })

我们先看一个示例需求代码如下: 希望每次点击删除按钮删除数组的元素后, ListView中其他item的展示信息不变(包括颜色和字体)

class _HomeScreenState extends State<HomeScreen> {

  List<String> names = ["111111", "222222", "333333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Key Demo"),
      ),
      body: ListView(
        children: names.map((name) {
          return ListItemLess(name);
        }).toList(),
      ),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        }
      ),
    );
  }
}

我们吧ListView的item分别使用StatelessWidget和StatefulWidget实现, 看看两者区别

StatelessWidget

我们先对ListItem使用一个StatelessWidget进行实现:

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(name, style: TextStyle(fontSize: 30, color: Colors.white)),
      color: randomColor,
    );
  }
}
  • 通过实践很明显, 每次删除第一个元素后, 虽然也能删除第一个ListItem, 剩余的每一个ListItem展示的信息也是对的, 但是他们的颜色却是每次都会发生变化
  • 这主要就是因为, 每次删除之后都会调用setState,也就会重新build,重新build出来的新的StatelessWidget`会重新生成一个新的随机颜色

StatefulWidget

现在对ListItem使用StatefulWidget实现同样的功能

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name): super();
  @override
  _ListItemFulState createState() => _ListItemFulState();
}

class _ListItemFulState extends State<ListItemFul> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(widget.name),
      color: randomColor,
    );
  }
}
  • 我们发现一个很奇怪的现象, 信息展示正常(删除了第一条数据),但是从颜色上看, 是删除了最后一条
  • 在我们每次调用setState的时候, Widget都会调用一个canUpdate函数判断是否需要重建element
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  • 在删除第一条数据的时候,Widget对应的Element并没有改变
  • 而目前是没有设置Key的, 所以Element中对应的State引用也没有发生改变
  • 在更新Widget的时候,Widget使用了没有改变的Element中的State, 也就是之前创建的三个element中的前两个
  • 这也就是为什么删除之后, 从颜色上看, 删除的是最后一条

添加Key

在上面ListItemFul的基础上, 为每一个ListItemFul加上一个key

class ListItemFulKey extends StatefulWidget {
  final String name;
  ListItemFulKey(this.name, {Key key}): super(key: key);

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


// 在上面使用的时候, 传入一个不同的key
ListItemFulKey(name, key: ValueKey(name))
  • 最终这就是我们想要实现的效果了
  • 上述代码中, 为每一个ListItemFulKey添加了一个key值, 而且每一个的Key值都是不一样的
  • 在删除一个元素调用setState方法后, 会重新build的一个Widget Tree
  • Element会拿到新的Widget Tree和原来保存的旧的Widget Tree做一个diff算法
  • 根据runtimeType和key进行比对, 和新的Widget Tree相同的会被继续复用, 否则就会调用unnmount方法删除

Key的分类

  • Key本身是一个抽象,不过它也有一个工厂构造器,创建出来一个ValueKey
  • 直接子类主要有:LocalKey和GlobalKey
    • LocalKey,它应用于具有相同父Element的Widget进行比较,也是diff算法的核心所在;
    • GlobalKey,通常我们会使用GlobalKey某个Widget对应的Widget或State或Element
@immutable
abstract class Key {
  /// 工厂构造函数
  const factory Key(String value) = ValueKey<String>;

  @protected
  const Key.empty();
}

abstract class LocalKey extends Key {
  /// Default constructor, used by subclasses.
  const LocalKey() : super.empty();
}

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key { }
LocalKey

LocalKey有三个子类

ValueKey:

  • ValueKey是当我们以特定的值作为key时使用,比如一个字符串、数字等等

ObjectKey:

  • 如果两个学生,他们的名字一样,使用name作为他们的key就不合适了
  • 我们可以创建出一个学生对象,使用对象来作为key

UniqueKey:

  • 如果我们要确保key的唯一性,可以使用UniqueKey
class ValueKey<T> extends LocalKey {
  const ValueKey(this.value);
}

class ObjectKey extends LocalKey {
  const ObjectKey(this.value);
}

class UniqueKey extends LocalKey {
  UniqueKey();
}
GlobalKey
  • GlobalKey可以帮助我们访问某个Widget的信息,包括Widget或State或Element等对象, 有点类似于React中的ref
  • 比如我们想在HomePage中访问HomeContenet中的widget
class HomePage extends StatelessWidget {

  final GlobalKey<_HomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GlobalKey Demo"),
      ),
      body: HomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          final message = homeKey.currentState.message;
          final name = homeKey.currentState.widget.name;
          print('message = $message, name = $name');
          homeKey.currentState.newPrint();

          final currentCtx = homeKey.currentContext;
          print('currentCtx = $currentCtx');
        }
      ),
    );
  }
}

class HomeContent extends StatefulWidget {

  final String name = 'homeContent';

  HomeContent({ Key key }): super(key: key);

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

class _HomeContentState extends State<HomeContent> {

  final String message = 'message';

  void newPrint() {
    print('new---print');
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

参考文档


欢迎您扫一扫下面的微信公众号,订阅我的博客!

微信公众号

全面理解-Flutter(万字长文,深度解析) 题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等录播视频免费分享出来。 阅读详情

相关推荐

Flutter面试题深度解析:从渲染原理到混合开发与工程化实战

Flutter作为跨端开发框架,其核心价值在于通过自绘引擎实现高性能UI渲染,同时借助Platform Channel桥接原生能力。理解Widget、Element、RenderObject三棵树的关系,能帮开发者定位渲染链路中的性能瓶颈;掌握热重载机制,则能快速规避"flutter热重载后浏览器没更新"等环境类陷阱。在工程应用中,混合开发与原生模块交互必不可少,例如"flutter 微信登录"就依赖MethodChannel正确回传生命周期事件;而遇到"cmake error"类报错时,需从原生工具链配置

weixin_34279246的博客 271

Flutter之StatelessWidget和StatefulWidget的详细使用

状态是在构建widget时可以同步读取的信息,或者在widget的生命周期中可能更改的信息,在Flutter中如果要管理状态需要用到 StatefulWidget。 什么是StatelessWidget? Flutter中的StatelessWidget是一个不需要状态更改的widget - 它没有要管理的内部状态。 当您描述的用户界面部分不依赖于对象本身中的配置信息以及widget的BuildC...

watertekhqx的博客 4643

2026年信创系统技术应用专业人才培养基地建设方案(高职、信创新专业、人才培养、专业代码:510221)

响应国家信创产业战略及教育部新增“信创系统技术应用”专业(代码510221)的部署,联合麒麟软件、华为鸿蒙等领军企业,建设高水平信创人才培养基地。方案基于国家政策导向(如2027年央企全面国产化替代目标)和行业人才缺口(2025年缺口超80万人),提出以“产教融合、创新驱动”为核心,构建集教学、实训、认证于一体的综合性培养体系。 建设内容涵盖五大方向: 专业规划:分三阶段(2025-2030年)推进专业申报、课程开发与区域辐射,最终建成区域性信创技术创新平台。 课程体系:围绕运维、云计算、数据库、适配

啊碧根果的博客 1596

【Flutter】自定义 Flutter 组件 ( 创建自定义 StatelessWidget、StatefulWidget 组件 | 调用自定义组件 )

一、Flutter 组件简介、 二、Flutter 自定义 StatelessWidget 组件流程、 1、导入父类包、 2、选择继承的父类、 3、设置成员变量及构造函数、 4、重写 build 方法、 5、完整代码示例、 三、Flutter 自定义 StatefulWidget 组件流程、 四、使用 final 修饰 Widget 组件成员变量分析、 五、调用自定义组件、 1、主要方法、 2、完整代码、 3、效果展示、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 6615

Python 打包与发布陷阱:如何用 setuptools 和 PyInstaller 生成跨平台可执行文件?

使用。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1206

从手机到自动驾驶:MIPI-CSI2协议演进史与车规级改造要点

本文追溯了MIPI-CSI2协议从智能手机摄像头到自动驾驶核心传感器的演进历程。重点剖析了该协议在车规级改造中面临的严苛挑战,包括物理层稳定性、EMC防护、功能安全与数据可靠性等关键要点,并探讨了其在多传感器融合与高带宽实时传输中的工程实践。

weixin_28318011的博客 307

跟我学flutter:细细品Widget(二)StatelessWidget&StatefulWidget

StatelessWidget和StatefulWidget是Flutter开发必不可少的。两者的区别在于state。有状态的widget需要继承StatefulWidget无状态的需要继承StatelessWidget。

s297165331的专栏 1355

探索热阻测试仪在半导体器件热管理中的应用与前景

T3Ster是MicReD研发的热测试仪,运用JEDEC稳态实时测试方法,专业测试分离或集成的双极型晶体管、MOS晶体管、常见的三极管、LED封装和半导体闸流管等器件的热特性。利用瞬态温升技术,可测得器件稳态热阻和温升,不但可以测得半导体器件稳态热阻和温升,而且可以直接测量各部分对于温升的贡献,计算芯片热流路径上的纵向热阻构成,对器件热可靠性设计、散热问题解决、产品性能提升和长期可靠性至关重要。关键词:T3ster,Micred,功率循环,结温测试,热阻测试,结温热阻测试,半导体热特性测试;

anscos_Olivia的博客 895

flutter - 构造函数

构造函数 class MyWidget extends StatefulWidget { final String userName; MyWidget({Key key, this.userName}) : super(key: key); _MyWidgetState createState() => _MyWidgetState(); } class _MyWidgetS...

ifredom 6403

部署资源管理系统chemx

资源管理系统的部署教程

weixin_40784215的博客 822

【100%通过率】华为OD机试真题 Python 实现【投篮大赛】【2022.11 Q4 新题】

你现在是一场采用特殊赛制投篮大赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中ops[i]是你需要记录的第i项操作,ops遵循下述规则: 整数x-表示本回合新获得分数x “+” – 表示本回合新获得的得分是前两次得分的总和。 “D” – 表示本回合新获得的得分是前一次得分的两倍。 “C” – 表示本回合没有分数,并且前一次得分无效,将其从记录中移除。 请你返回记录中所有得分的总和。

MISAYAONE的博客 1万+

Flutter-02-Flutter有状态的StatefulWidget

flutter

qq_27909209的博客 1424

Allegro172版本DFM功能介绍

Allegro172版本提供了实时DFM检查的功能,让违反要求的地方实时以DRC的形式报出来,让加工不可加工因素在设计的时候同步避免。打开Constraint,选择Manufacturing, 所有DFM相关DRC都在这。把所有下面DRC开关(DFM,DFA,DFT)都选择on,选择OK。这个规则和物理和间距规则等等都没有关系,单独的一个加工规则。这些DRC大部分是正常的,正常的可以去修改规则来消除。等待DRC刷新,板子上会出现很多DRC,类似下图。下面介绍如何调用和产生DFM规则。一直往下点击next。

weixin_54787054的博客 1337

Flutter:基础,Widget树,Element树,Render树

Flutter渲染原理。Widget的声明周期。

sunluyi的博客 226

YOLOv8/v9工业零件缺陷检测实战指南:从原理到产线部署

摘要:本文系统介绍基于YOLOv8/v9的工业零件缺陷检测技术,从工业质检背景与需求出发,详解缺陷检测原理、算法构建与模型训练,提供从数据集准备到移动端部署的全流程实操方案。包含多尺度检测、纹理分析等高级技术,适配工业相机的环境优化策略,以及完整质检系统代码实现。适合工业视觉领域新手与进阶开发者,助力快速构建高鲁棒性的缺陷检测系统。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1304

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

《Flutter全栈开发实战指南:从零到高级》- 04 - Widget核心概念与生命周期

本文深入浅出地讲解了Flutter开发中Widget的核心概念,重点对比了StatelessWidget(静态组件)和StatefulWidget(动态组件)的区别与适用场景,并通过实际代码示例演示了它们的具体用法。

技术探索者之路 - 一个专注于软件开发技术的实践型博客,涵盖移动应用开发、后端架构、Python编程和算法数据结构等核心技术领域。欢迎各位道友光临,喜欢本博主的别忘了一键三连哦。 629

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【Flutter】关注的那些点

第一次接触Flutter 应该是19年,被声明式语法所吸引,后来苦于事件成本的问题那篇Fltutter 状态管理的Provider 就一直创建在哪里,没有理会他。也一直没空去写。22年因为某些原因,去了某厂,主要的跨平台技术栈事更没有时间去关注,flutter。在这中厂的一年多时间也就是9月份,迎来了至暗时刻 – 开源节流也就有了些空余时间,也就想总结一下关于Flutter东西。

jeversonjee's blog 631

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

第四章:深入理解Widget(一) -- Widget概述和渲染

理论讲解了Widget的渲染过程实现原理

Waruler的博客 5782

【Flutter入门】3.深入理解Flutter的核心概念:架构、渲染与生命周期

探索Flutter的核心架构,揭秘其三层分明的架构设计、精妙的渲染流程和完整的生命周期管理。通过生动的比喻和实战示例,带你深入理解Flutter的运作机制,助你构建高性能的跨平台应用。

xichen_knight的博客 2103

Flutter三棵树核心详解之Widget树完全解析(一)

这篇博客主要介绍Flutter三棵树中的Widget树。

我叫柱子哥 426

Flutter核心原理

Flutter核心原理 1. 硬件绘图原理 显示器成相的原理就是在不同的物理像素点上显示不同的颜色,最终构成完整的图像。 为了更新显示画面,显示器是以固定的频率刷新(从GPU取数据),比如有一部手机屏幕的刷新频率是 60Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(如VSync), 60Hz的屏幕就会一秒内发出 60次这样的信号。而这个信号主要是用于同步CPU、GPU和显示器的。一般地来说,计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给

jojoliya的专栏 1577

Flutter:三棵树彻底拆解(Widget / Element / RenderObject)

作为 Flutter 开发者,我们每天都在和Text、Container、ListView打交道,写着一层套一层的 Widget 嵌套代码。但你有没有想过:这些写好的 Widget,是如何最终变成屏幕上可见的界面。

linwenglong7155的博客 510

Flutter 中的三棵树(Widget/Element/RenderObject)深度剖析

Widget 树:频繁重建,只负责描述配置;Element 树:按需更新,负责判断是否复用;RenderObject 树:极少重建,负责核心渲染工作。

. 1666

Flutter学习之视图体系

一、前言 经过之前的学习,可以知道Flutter是一种全新的响应式跨平台的移动开发框架,越来越多的开发者参与学习或者研究中,确实在iOS和Android平台上能够用一套代码构建出性能比较高的应用程序。我刚开始接触FlutterFlutter中文网看到这么一句话:Widget是Flutter应用程序用户界面的基本构建块。每个Widget都是用户界面一部分的不可变声明。与其他将试图、控制器、布局和其他...

深红骑士的博客 1883

Flutter 07 框架和三棵树(Widgets、Elements和RenderObjects)

在Flutter中和Widgets一起协同工作的还有另外两个伙伴:Elements和RenderObjects;由于它们都是有着树形结构,所以经常会称它们为三棵树。1)Widget:Widget是Flutter的核心部分,是用户界面的不可变描述。做Flutter开发接触最多的就是 Widget,可以说Widget撑起了Flutter的半边天;

sziitjin的博客 815

Flutter学习三-Flutter基本结构和原理

Flutter有三种运行模式debug 调试模式profile 分析模式release 运行模式调试模式特点:原生平台:启用断言启用调试编译针对快速开发和运行周期进行了优化(但不针对执行速度、二进制大小或部署)。网络应用程序构建没有缩小该应用程序使用 dartdevc 编译器进行编译,以便于调试。release模式基本上就是调试模式反过来原生版本web版本该应用程序是用 dart2js 编译器编译的,以获得最佳性能。分析模式。......

hn_lgc的专栏 1174
上一篇: Flutter开发之Dart中的类和对象
下一篇: Flutter 必备开源项目推荐
CodeTitan
博客等级 码龄9年 51粉丝 102原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值