04 - 06 Flutter開發初體驗 & Widget 生命週期

zeqinjie發表於2021-09-01

前言

本月繼續對 Flutter CoderWhy 視訊教程 學習做個筆記~ 慣例還是會提幾個 question 思考??

Flutter 兩種風格

Flutter 設計兩種風格 App :Material Design 風格 MaterialApp 和 iOS 風格的 CupertinoApp

Scaffold

Scaffold 腳手架:主要用來定義頁面的基礎結構,比如上導航、內容區域、下導航、側邊欄

  • 有相關屬性:appbar、drawer、body
import 'package:flutter/material.dart';

main() {
  // runApp 函式
  runApp(MaterialApp(
    // debugShowCheckedModeBanner: false, // 控制介面右上角是否顯示`debug`提示
    home: Scaffold(
      appBar: AppBar(
        title: Text("第一個Flutter程式"),
      ),
      body: Center(
        child: Text(
          "Hello World",
          textDirection: TextDirection.ltr,
          style: TextStyle(fontSize: 30, color: Colors.orange),
        ),
      ),
    ),
  ));
}
複製程式碼

Widget

Flutter 中萬物皆Widget (元件/部件) Widget 有兩種:StatelessWidget && StatefulWidget

StatelessWidget

沒有狀態改變的 Widget,通常是做一些展示工作

為什麼 StatelessWidget 是不可以變的?

  • StatelessWidget 繼承至 Widget
  • Widget 是 @immutable 修飾的,不可以變的。所以 StatelessWidget 是不可變的
  • 繼承 StatelessWidget 子類,必須要實現 Widget build(BuildContext context) 抽象方法

StatelessWidget & Widget 的原始碼

@immutable
abstract class Widget extends DiagnosticableTree {
	// ...
}

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}
複製程式碼

生命週期

先呼叫 建構函式,再呼叫 build

class ZQLifeCycleStatelessWidget extends StatelessWidget {

  final String message;

  ZQLifeCycleStatelessWidget(this.message) {
    print("建構函式被呼叫");
  }

  @override
  Widget build(BuildContext context) {
    print("呼叫build方法");
    return Text(this.message);
  }
}
複製程式碼

StatefulWidget

有狀態改變的 Widget, 通常做互動變化狀態,或者頁面依據 data 重新整理展示

為什麼 StatefullWidget 是可變的?

  • ? 其實無論 StatelessWidget 還是 StatefulWidget,其父類都是 Widget 因此它定義成員變數也是 final 修飾不可變~
  • 但是繼承 StatefulWidget 的子類,必需要實現 State createState(); 抽像方法。
    • 所以可以變的是 State 這也是與 StatelessWidget 不一樣的地方

StatefulWidget 的原始碼

abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key key }) : super(key: key);

  /// Creates a [StatefulElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatefulElement createElement() => StatefulElement(this);

  /// Creates the mutable state for this widget at a given location in the tree.
  ///
  /// Subclasses should override this method to return a newly created
  /// instance of their associated [State] subclass:
  ///
  /// ```dart
  /// @override
  /// _MyState createState() => _MyState();
  /// ```
  ///
  /// The framework can call this method multiple times over the lifetime of
  /// a [StatefulWidget]. For example, if the widget is inserted into the tree
  /// in multiple locations, the framework will create a separate [State] object
  /// for each location. Similarly, if the widget is removed from the tree and
  /// later inserted into the tree again, the framework will call [createState]
  /// again to create a fresh [State] object, simplifying the lifecycle of
  /// [State] objects.
  @protected
  @factory
  State createState();
}
複製程式碼

State 的原始碼

abstract class State<T extends StatefulWidget> with Diagnosticable {
	// ...
  /// an argument.
  T get widget => _widget;
  T _widget;

  _StateLifecycle _debugLifecycleState = _StateLifecycle.created;

  BuildContext get context => _element;
  StatefulElement _element;

  bool get mounted => _element != null;

  @protected
  @mustCallSuper
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }

  @mustCallSuper
  @protected
  void didUpdateWidget(covariant T oldWidget) { }

  @mustCallSuper
  void reassemble() { }

  @protected
  void setState(VoidCallback fn) {
    assert(fn != null);
    assert(() {
      if (_debugLifecycleState == _StateLifecycle.defunct) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called after dispose(): $this'),
          ErrorDescription(
              'This error happens if you call setState() on a State object for a widget that '
                  'no longer appears in the widget tree (e.g., whose parent widget no longer '
                  'includes the widget in its build). This error can occur when code calls '
                  'setState() from a timer or an animation callback.'
          ),
          ErrorHint(
              'The preferred solution is '
                  'to cancel the timer or stop listening to the animation in the dispose() '
                  'callback. Another solution is to check the "mounted" property of this '
                  'object before calling setState() to ensure the object is still in the '
                  'tree.'
          ),
          ErrorHint(
              'This error might indicate a memory leak if setState() is being called '
                  'because another object is retaining a reference to this State object '
                  'after it has been removed from the tree. To avoid memory leaks, '
                  'consider breaking the reference to this object during dispose().'
          ),
        ]);
      }
      if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called in constructor: $this'),
          ErrorHint(
              'This happens when you call setState() on a State object for a widget that '
                  "hasn't been inserted into the widget tree yet. It is not necessary to call "
                  'setState() in the constructor, since the state is already assumed to be dirty '
                  'when it is initially created.'
          ),
        ]);
      }
      return true;
    }());
    final dynamic result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
              'The setState() method on $this was called with a closure or method that '
                  'returned a Future. Maybe it is marked as "async".'
          ),
          ErrorHint(
              'Instead of performing asynchronous work inside a call to setState(), first '
                  'execute the work (without updating the widget state), and then synchronously '
                  'update the state inside a call to setState().'
          ),
        ]);
      }
      // We ignore other types of return values so that you can do things like:
      //   setState(() => x = 3);
      return true;
    }());
    _element.markNeedsBuild();
  }

  @protected
  @mustCallSuper
  void deactivate() { }

  @protected
  @mustCallSuper
  void dispose() {
    assert(_debugLifecycleState == _StateLifecycle.ready);
    assert(() {
      _debugLifecycleState = _StateLifecycle.defunct;
      return true;
    }());
  }

  @protected
  Widget build(BuildContext context);

  @protected
  @mustCallSuper
  void didChangeDependencies() { }

	// ..
}
複製程式碼

question

我們都知道在 StatefullWidget 中更新資料想讓介面變化需要呼叫 setState,這是為什麼呢?

setState 涉及的原始碼

void markNeedsBuild() {
  assert(_debugLifecycleState != _ElementLifecycle.defunct);
  if (!_active)
    return;
 // ...     
  if (dirty)
    return;
  _dirty = true;
  owner.scheduleBuildFor(this);
}

void scheduleBuildFor(Element element) {
    ...
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    // ...
}

void scheduleFrame() {
  if (_hasScheduledFrame || !_framesEnabled)
    return;
  assert(() {
    if (debugPrintScheduleFrameStacks)
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    return true;
  }());
  window.scheduleFrame();
  _hasScheduledFrame = true;
}

void _handleBuildScheduled() {
  //呼叫ensureVisualUpdate
  ensureVisualUpdate();
}

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      //當schedulerPhase為SchedulerPhase.idle,
      //SchedulerPhase.postFrameCallbacks時呼叫scheduleFrame()
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}
複製程式碼

所以對上面原始碼我做了總結:

  • 從原始碼看出 StatefullWidget 需要實現 createState 方法,而 State 的子類是可以是儲存狀態變數的,所以可以看出其與 StatelessWidget 區別
  • 對上述問題:StatefullWidget 呼叫 setState 原因~
    • 因為呼叫 setState 呼叫過程 markNeedsBuild => onBuildScheduled => scheduleFrame => drawFrame (具體參看 Flutter的setState更新原理和流程
    • UI 執行緒的繪製過程的核心是執行 WidgetsBinding 的 drawFrame 方法,然後會建立 layer tree 檢視樹

生命週期

class ZQLifeCycleStatefullWidget extends StatefulWidget {

  ZQLifeCycleStatefullWidget() {
    print("1.呼叫 ZQLifeCycleStatefullWidget 的 constructor 方法");
  }

  @override
  _ZQLifeCycleStatefullWidgetState createState() {
    print("2.呼叫 ZQLifeCycleStatefullWidget 的 createState 方法");
    return _ZQLifeCycleStatefullWidgetState();
  }
}

class _ZQLifeCycleStatefullWidgetState extends State<ZQLifeCycleStatefullWidget> {

  _ZQLifeCycleStatefullWidgetState() {
    print("3. 呼叫 ZQLifeCycleStatefullWidgetState 的 constructor 方法");
  }

  @override
  void initState() {
    // TODO: implement initState ? 這裡必須呼叫 super(@mustCallSuper)
    super.initState();
    print("4. 呼叫 ZQLifeCycleStatefullWidgetState 的 initState 方法");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("呼叫 ZQLifeCycleStatefullWidgetState 的 didChangeDependencies 方法");
  }

  @override
  void didUpdateWidget(covariant ZQLifeCycleStatefullWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print("呼叫 ZQLifeCycleStatefullWidgetState 的 didUpdateWidget 方法");
  }

  @override
  Widget build(BuildContext context) {
    print("5. 呼叫 ZQLifeCycleStatefullWidgetState 的 build 方法");
    return Text("ZQLifeCycleStatefullWidgetState");
  }

  @override
  void dispose() {
    super.dispose();
    print("6. 呼叫 ZQLifeCycleStatefullWidgetState 的 dispose 方法");
  }
}
複製程式碼
flutter: 1.呼叫 ZQLifeCycleStatefullWidget 的 constructor 方法
flutter: 2.呼叫 ZQLifeCycleStatefullWidget 的 createState 方法
flutter: 3. 呼叫 ZQLifeCycleStatefullWidgetState 的 constructor 方法
flutter: 4. 呼叫 ZQLifeCycleStatefullWidgetState 的 initState 方法
flutter: 呼叫 ZQLifeCycleStatefullWidgetState 的 didChangeDependencies 方法
flutter: 5. 呼叫 ZQLifeCycleStatefullWidgetState 的 build 方法
>>>> 點選計數按鈕
flutter: 1.呼叫 ZQLifeCycleStatefullWidget 的 constructor 方法
flutter: 呼叫 ZQLifeCycleStatefullWidgetState 的 didUpdateWidget 方法
flutter: 5. 呼叫 ZQLifeCycleStatefullWidgetState 的 build 方法
複製程式碼

流程圖

image.png

依次觸發順序

  • createState
    • 初始化構造時候會觸發
  • initState
    • 注意:在 override initState 的時候必須要呼叫 super.initState():
    • 類似 iOS viewDidLoad
    • 此時 mount 為true
  • didChangeDependencies
    • 呼叫initState會呼叫;
    • 從其他物件中依賴一些資料發生改變時,比如 InheritedWidget
  • build
  • addPostFrameCallback
  • didUpdateWidget
    • 執行 didUpdateWidget 方法是在當父Widget觸發重建(rebuild)時,系統會呼叫 didUpdateWidget 方法
  • deactivate
  • dispose

question

為什麼在 override initState 的時候必須要呼叫 super.initState():?

  • 原因是: @mustCallSuper 註解作用,要求子類必需呼叫父類方法

方法說明

摘抄至: 傳送門

方法說明
createStateFramework 會通過呼叫StatefulWidget.createState 來建立一個 State。
initState新建立的 State 會和一個 BuildContext 產生關聯,此時認為 State 已經被安裝好了,initState 函式將會被呼叫。通常,我們可以重寫這個函式,進行初始化操作。
didChangeDependencies在 initState 呼叫結束後,這個函式會被呼叫。事實上,當 State 物件的依賴關係發生變化時,這個函式總會被 Framework 呼叫。
build經過以上步驟,系統認為一個 State 已經準備好了,就會呼叫 build 來構建檢視。我們需要在這個函式中返回一個 Widget。
deactivatedeactivate 當 State 被暫時從檢視樹中移除時,會呼叫這個函式。頁面切換時,也會呼叫它,因為此時 State 在檢視樹中的位置發生了變化,需要先暫時移除後新增。
dispose當 State 被永久地從檢視樹中移除時,Framework 會呼叫該函式。在銷燬前觸發,我們可以在這裡進行最終的資源釋放。在呼叫這個函式之前,總會先呼叫 deactivate 函式。
didUpdateWidget當 Widget 的配置發生變化時,會呼叫這個函式。比如,熱過載的時候就會呼叫這個函式。呼叫這個函式後,會呼叫 build 函式。
setState當需要更新 State 的檢視時,需要手動呼叫這個函式,它會觸發 build 函式。

StatefulWidget & State 流程關係圖

image.png

quesion

在 setState 中 mounted 的作用是?

總結:

  • StatelessWidget 是不可變的而 StatefullWidget 的狀態是可變的,主要原因是是和其重寫的抽象方法有關
    • StatelessWidget:Widget build(BuildContext context)
    • StatefullWidget: State createState()
  • widget 最終渲染的東西是什麼看的是 build 方法裡返回的是什麼,比如有的返回是 RenderObjectWidget 如果是 StatefullWidget 的看的是 state 返回的 build

快捷鍵

  • 輸入 stlstful 快捷鍵快速建立 widget
  • alt + enter 包裹元件
  • option + enter 將 StatelessWidget 轉 StatefullWidget
  • option + enter + w 抽成 widget
  • command + alt + b 檢視抽象類的實現類

宣告式程式設計 & 指令式程式設計

  • 指令式程式設計的主要思想是關注執行的步驟,即一步一步告訴計算機先做什麼再做什麼
  • 宣告式程式設計是以資料結構的形式來表達程式執行的邏輯。應該做什麼,但不指定具體要怎麼做

程式碼示例

傳送門

參考

相關文章