前言
本月繼續對 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 是不可變的
- @immutable: 註解標明的類或者子類都必須是不可變的 傳送門
- 所以 Widget 定義的成員變數必須使用 final 定義
- 題外話(註解應用):Flutter 註解處理及程式碼生成
- 繼承 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 方法
複製程式碼
流程圖
依次觸發順序
- 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 註解作用,要求子類必需呼叫父類方法
方法說明
摘抄至: 傳送門
方法 | 說明 |
---|---|
createState | Framework 會通過呼叫StatefulWidget.createState 來建立一個 State。 |
initState | 新建立的 State 會和一個 BuildContext 產生關聯,此時認為 State 已經被安裝好了,initState 函式將會被呼叫。通常,我們可以重寫這個函式,進行初始化操作。 |
didChangeDependencies | 在 initState 呼叫結束後,這個函式會被呼叫。事實上,當 State 物件的依賴關係發生變化時,這個函式總會被 Framework 呼叫。 |
build | 經過以上步驟,系統認為一個 State 已經準備好了,就會呼叫 build 來構建檢視。我們需要在這個函式中返回一個 Widget。 |
deactivate | deactivate 當 State 被暫時從檢視樹中移除時,會呼叫這個函式。頁面切換時,也會呼叫它,因為此時 State 在檢視樹中的位置發生了變化,需要先暫時移除後新增。 |
dispose | 當 State 被永久地從檢視樹中移除時,Framework 會呼叫該函式。在銷燬前觸發,我們可以在這裡進行最終的資源釋放。在呼叫這個函式之前,總會先呼叫 deactivate 函式。 |
didUpdateWidget | 當 Widget 的配置發生變化時,會呼叫這個函式。比如,熱過載的時候就會呼叫這個函式。呼叫這個函式後,會呼叫 build 函式。 |
setState | 當需要更新 State 的檢視時,需要手動呼叫這個函式,它會觸發 build 函式。 |
StatefulWidget & State 流程關係圖
quesion
在 setState 中 mounted 的作用是?
總結:
- StatelessWidget 是不可變的而 StatefullWidget 的狀態是可變的,主要原因是是和其重寫的抽象方法有關
- StatelessWidget:
Widget build(BuildContext context)
- StatefullWidget:
State createState()
- StatelessWidget:
- widget 最終渲染的東西是什麼看的是 build 方法裡返回的是什麼,比如有的返回是
RenderObjectWidget
如果是 StatefullWidget 的看的是 state 返回的 build
快捷鍵
- 輸入
stl
或stful
快捷鍵快速建立 widget alt
+enter
包裹元件option
+enter
將 StatelessWidget 轉 StatefullWidgetoption
+enter
+w
抽成 widgetcommand
+alt
+b
檢視抽象類的實現類
宣告式程式設計 & 指令式程式設計
- 指令式程式設計的主要思想是關注執行的步驟,即一步一步告訴計算機先做什麼再做什麼
- 宣告式程式設計是以資料結構的形式來表達程式執行的邏輯。應該做什麼,但不指定具體要怎麼做