flutter Widget基礎

首先,我們先附上官網教程中關於flutter框架的介紹,作爲補充。

Flutter官方提供的Flutter框架圖
在這裏插入圖片描述
Flutter Framework
這是一個純 Dart實現的 SDK:

Foundation和Animation、Painting、Gestures 在Google的一些視頻中被合併爲一個dart UI層,對應的是Flutter中的dart:ui包,它是Flutter引擎暴露的底層UI庫,提供動畫、手勢及繪製能力。

Rendering層是一個抽象的佈局層,它依賴於dart UI層,Rendering層會構建一個UI樹,當UI樹有變化時,會計算出有變化的部分,然後更新UI樹,最終將UI樹繪製到屏幕上,這個過程類似於React中的虛擬DOM。

Widgets層是Flutter提供的的一套基礎組件庫,在基礎組件庫之上,Flutter還提供了 Material 和Cupertino兩種視覺風格的組件庫。

Flutter Engine
這是一個純 C++實現的 SDK,其中包括了 Skia引擎、Dart運行時、文字排版引擎等。在代碼調用 dart:ui庫時,調用最終會走到Engine層,然後實現真正的繪製邏輯。

在我們學習flutter的過程中,很多老師大咖們最喜歡說的一句話就是flutter一切皆widget。
Flutter中Widget不僅可以表示UI元素,也可以表示一些功能性的組件如:用於手勢檢測的 GestureDetector widget、用於APP主題數據傳遞的Theme等等,而原生開發中的控件通常只是指UI元素。

我們先來看一下Widget類的聲明:

/// Describes the configuration for an [Element].
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;
  @protected
  Element createElement();
  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

其中有一句註釋我特別給大家列出來了:Describes the configuration for an [Element].

在Flutter中,Widget的功能是“描述一個UI元素的配置數據”,也就是說,Widget其實並不是表示最終繪製在設備屏幕上的顯示元素,而它只是描述顯示元素的一個配置數據。實際上在flutter中真正代表屏幕上顯示元素的類是Element。對於我們這些希望能夠達到使用目的的初學者來說,我覺得沒有必要過於深究。

我們只需要知道:Widget只是UI元素的一個配置數據,並且一個Widget可以對應多個Element。這是因爲同一個Widget對象可以被添加到UI樹的不同部分,而真正渲染時,UI樹的每一個Element節點都會對應一個Widget對象。

再來看看類接口:

widget類繼承自DiagnosticableTree,DiagnosticableTree即“診斷樹”,主要作用是提供調試信息。

Key: 這個key屬性類似於React/Vue中的key,主要的作用是決定是否在下一次build時複用舊的widget,決定的條件在canUpdate()方法中。

createElement**()**:Flutter Framework在構建UI樹時,會先調用此方法生成對應節點的Element對象。此方法是Flutter Framework隱式調用的,在我們開發過程中基本不會調用到。

debugFillProperties(…) 複寫父類的方法,主要是設置診斷樹的一些特性。

**canUpdate(…)**是一個靜態方法,可以理解爲是否用新的Widget對象去更新舊UI樹上所對應的Element對象的配置。

Widget 類本身是一個抽象類,其中最核心的就是定義了createElement()接口,在Flutter開發中,我們一般都不用直接繼承Widget類來實現一個新組件,相反,我們通常會通過繼承StatelessWidget或StatefulWidget來間接繼承Widget類來實現。

StatelessWidget和StatefulWidget都是直接繼承自Widget類,而這兩個類也正是Flutter中非常重要的兩個抽象類,它們引入了兩種Widget模型:有狀態的組件(Stateful widget) 和無狀態的組件(Stateless widget)。

無狀態的組件(Stateless widget)

StatelessWidget用於不需要維護狀態的場景,也就是說它初始化後就不會更改,比如標題等等。一般情況下,如果widget對應的ui不需要進行更新時,那麼我們就應該使用StatelessWidget。

它通常在build方法中通過嵌套其它Widget來構建UI,在構建過程中會遞歸的構建其嵌套的Widget。

StatelessWidget繼承自Widget類,重寫了createElement()方法:

@override
StatelessElement createElement() => new StatelessElement(this);

StatelessElement 間接繼承自Element類,與StatelessWidget相對應(作爲其配置數據)。

接下來我們看一個簡單的示例:

import  "package:flutter/material.dart";

class QStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("hello flutter");
  }
}

在main.dart中使用:
1.導入自定義的widget
2.在需要使用的地方創建對應的實例

import "package:flutter/material.dart";
import 'basic/QStatelessWidget.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text("Flutter 基礎"),
          ),
          body: Center(
            child:QStatelessWidget(),
          ),
        ));
  }
}

運行結果:

在這裏插入圖片描述

顯然這樣簡單的使用並不能滿足我們的胃口,接下來我們在看看怎麼使用widget的構造函數進行傳參。

按照慣例,widget的構造函數參數應使用命名參數,命名參數中的必要參數要添加@required標註,這樣有利於靜態代碼分析器進行檢查。另外,在繼承widget時,第一個參數通常應該是Key,另外,如果Widget需要接收子Widget,那麼child或children參數通常應被放在參數列表的最後。同樣是按照慣例,Widget的屬性應儘可能的被聲明爲final,防止被意外改變。

import "package:flutter/material.dart";

class QStatelessWidget extends StatelessWidget {
  QStatelessWidget({Key key, @required this.text, this.fontColor: Colors.green})
      : super(key: key);

  final String text;
  final Color fontColor;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Text(
          text,
          style: TextStyle(
              color: fontColor,
              fontSize: 24,
              fontFamily: "Courier",
              decoration: TextDecoration.underline,
              decorationStyle: TextDecorationStyle.dashed),
        ),
      ),
    );
  }
}

其中構造函數:

 QStatelessWidget({Key key, @required this.text, this.fontColor: Colors.green})
      : super(key: key);

這裏使用的是dart的語法糖,(詳細請參考dart的相關文檔)等價於下面這種形式:

QStatelessWidget(String text, Color fontColor=Colors.green){
	this.text = text;
	this.fontColor = fontColor;
}

this.fontColor: Colors.green相當於其他語言中的默認參數值,這點大家想必都知道。

build方法有一個context參數,它是BuildContext類的一個實例,表示當前widget在widget樹中的上下文,每一個widget都會對應一個context對象(因爲每一個widget都是widget樹上的一個節點)。實際上,context是當前widget在widget樹中位置中執行”相關操作“的一個句柄,比如它提供了從當前widget開始向上遍歷widget樹以及按照widget類型查找父級widget的方法

具體的代碼沒有什麼看的,這裏就不在贅述了。在使用的地方我們使用命名參數傳遞對應的參數即可。

在這裏插入圖片描述
如果我們沒有使用命名參數,那麼會得到下面的錯誤提示,:
在這裏插入圖片描述

運行效果:
在這裏插入圖片描述

有狀態的組件(Stateful widget)

StatefulWidget又被稱爲有狀態組件,開發者可以根據用戶的操作來選擇性的更新界面上的組件。

和StatelessWidget一樣,StatefulWidget也是繼承自Widget類,並重寫了createElement()方法,不同的是返回的Element 對象並不相同;另外StatefulWidget類中添加了一個新的接口createState()。

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

  @override
  StatefulElement createElement() => new StatefulElement(this);

  @protected
  State createState();
}

接下來我們創建一個基於statefulwidget的例子。剛創建出來的文件可能向下面的例子一樣簡單,我們一步步開始。

在這裏插入圖片描述

一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀態

createState() 用於創建和Stateful widget相關的狀態,它在Stateful widget的生命週期中可能會被多次調用。例如,當一個Stateful widget同時插入到widget樹的多個位置時,Flutter framework就會調用該方法爲每一個位置生成一個獨立的State實例,其實,本質上就是一個StatefulElement對應一個State實例。

State中的保存的狀態信息的用處:
1.在widget 構建時可以被同步讀取。
2.在widget生命週期中可以被改變,當State被改變時,可以手動調用其setState()方法通知Flutter framework狀態發生改變,Flutter framework在收到消息後,會重新調用其build方法重新構建widget樹,從而達到更新UI的目的。

接下來我們來個簡單的例子:

import 'package:flutter/material.dart';

class QStatefulWidget extends StatefulWidget {
  const QStatefulWidget({Key key, this.current: 0}) : super(key: key);

  final int current;

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

class _QStatefulWidgetState extends State<QStatefulWidget> {
  int current;

  final welcomeArray = const [
    "Welcome,first.",
    "Welcome,second.",
    "Welcome,third.",
    "Welcome,fourth."
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text(welcomeArray[current]),
          onPressed: () => setState(this.handlePressed),
        ),
      ),
    );
  }

  void handlePressed() {
    if (current == welcomeArray.length - 1) {
      current = 0;
    } else {
      ++current;
    }
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    current = widget.current;
  }
}

代碼十分簡單,在屏幕中央顯示一個扁平按鈕,當點擊的時候我們會切換其上面顯示的文字。

在main.dart中指定其初始顯示的文字索引

在這裏插入圖片描述

運行效果:
在這裏插入圖片描述

點擊後:
在這裏插入圖片描述

作爲我們日後使用最多的widget,我們有必要了解一下它的生命週期,先貼一張StatefulWidget生命週期圖
在這裏插入圖片描述

ok,我們將其中用到的方法重載,添加log語句,直觀的看下其各個狀態的變化。

 @override
  void initState() {
    // TODO: implement initState
    super.initState();
    print("initState function run");
    current = widget.current;
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("didChangeDependencies function run");
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    print("dispose function run");
  }

  @override
  void reassemble() {
    // TODO: implement reassemble
    super.reassemble();
    print("reassemble function run");
  }

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
    print("deactivate function run");
  }

  @override
  void didUpdateWidget(QStatefulWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget function run");
  }

重新運行程序,打印內容如下:
在這裏插入圖片描述

點擊按鈕:

在這裏插入圖片描述

在main.dart中移除該widget
在這裏插入圖片描述
熱加載後輸出:
在這裏插入圖片描述

initState:當Widget第一次插入到Widget樹時會被調用,對於每一個State對象,Flutter framework只會調用一次該回調,所以,通常在該回調中做一些一次性的操作,如狀態初始化、訂閱子樹的事件通知等。

didChangeDependencies:當State對象的依賴發生變化時會被調用。

build:用於構建Widget子樹的,會在如下場景被調用:

1.調用initState()之後
2.調用didUpdateWidget()之後。
3.調用setState()之後。
4.調用didChangeDependencies()之後。
5.State對象從樹中一個位置移除後(會調用deactivate)又重新插入到樹的其它位置之後。

reassemble:在熱重載(hot reload)時會被調用,此回調在Release模式下永遠不會被調用。

didUpdateWidget:在widget重新構建時,Flutter framework會調用Widget.canUpdate來檢測Widget樹中同一位置的新舊節點,然後決定是否需要更新,如果Widget.canUpdate返回true則會調用此回調。

deactivate:當State對象從樹中被移除時,會調用此回調。

dispose:當State對象從樹中被永久移除時調用。

更多的內容我們暫時不深入探究,我們只要瞭解了其各個狀態即可。

另外,Flutter Widget build方法可能會執行多次,所以在build方法內做的事情應該儘可能的少。

在使用StatefulWidget的過程中將會調用到setState((){}) 的代碼儘可能的和要更新的視圖封裝在一個儘可能小的模塊裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章