Flutter之Widget概念

在Flutter框架中,Widget代表最基礎的部分,相當于是應用程序的基石.引用一句話“萬物皆是Widget”,就能看出Widget的重要性不言而喻,所以本篇著重講一下,我在學習Flutter的過程中,對Widget的一些理解和認知.有些基本知識直接摘自Flutter中文網


1.Widget的劃分

import 'package:flutter/material.dart'; //安卓類型的風格庫
void main() {
  runApp(
    new Center(
      child: new Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

眼熟吧? HelloWorld,程序員標準入門,上面的例子中,將一個widget傳遞給runApp函數就能構成一個最簡單的Flutter程序.

上面的例子中,該runApp函數接收給定的widget并使其成為widget樹的根,這就類似于iOS中,指定window的rootViewController.(我是這樣理解的).

常用的Widget:

1.Text:文本格式的widget
2.Row,Column:類似web開發中的盒模型FlexBox,讓你可以在水平或者垂直方向上靈活布局.
3.Stack:取代線性布局,Stack允許子widget堆疊,你可以使用Positioned來定位他們相對于Stack的上左下右四條邊的位置.
4.Container:它可以讓你創建一個矩形元素.它可以被裝飾為一個BoxDecoration,如background、一個邊框或者一個陰影.它同樣具有margins、padding和應用于其大小的約束constraints.

1.1.StatelessWidget和StatefulWidget

statelessWidget:表示無狀態的widget,內部沒有保存狀態,UI界面一經創建不會發生改變.

statefulWidget:有狀態的widget,內部有保存狀態,當狀態發生改變,調用setState()方法,會觸發更新UI界面的操作.另外對于自定義繼承自StatefulWidget的子類,必須要重寫createState()方法.

StatelessWidget示例
import 'package:flutter/material.dart';

void main() => runApp(MyStatelessWidget(text: "Welcome"));

class MyStatelessWidget extends StatelessWidget {
  final String text;
  MyStatelessWidget({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        text,
        textDirection: TextDirection.ltr,
      ),
    );
  }
}

在上面的示例中,使用了MyStatelessWidget類的構造函數傳遞標記為final的text。這個類繼承了StatelessWidget,它包含不可變數據。

statelessWidget的build方法通常只會在以下三種情況調用:

  • 將widget插入樹中時
  • 當widget的父級更改其配置時
  • 當它依賴的InheritedWidget發生變化時
StatefulWidget示例
class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

  _HomePageState createState() => _HomePageState();
}
------------------------------------------------------------------------
class _HomePageState extends State<HomePage> {
  int count=0;
  @override
  Widget build(BuildContext context) {
    return Container(
          
      child:Column(
        children: <Widget>[
          Chip(
            label: Text("${this.count}")           
          ),
          RaisedButton(
            child: Text('增加'),
            onPressed: (){             
              setState(() {
                 this.count++;
              });
            },
          )          
        ],
      )
    );
  }
}

上面的示例中,虛線上面的部分,聲明了一個StatefulWidget,它需要一個createState()方法。此方法創建管理widget狀態的狀態對象_HomePageState 。


2.如何判斷使用statelessWidget還是StatefulWidget的條件?

在Flutter中,widget是有狀態的還是無狀態的 , 取決于是否他們依賴于狀態的變化。
1.如果用戶交互或數據改變導致widget改變,那么它就是有狀態的。
2.如果一個widget是最終的或不可變的,那么它就是無狀態。

首先需要判斷widget的狀態,簡單點,沒有數據驅動,信息不可變的,都是statelessWidget,Flutter本身也告訴你,優先使用StatelessWidget。

還有一個重要的條件,Flutter并沒有實現數據雙向綁定,當你使用StatefulWidget時,你在 State.setState((){}) 中寫什么代碼都不重要,它僅用來標記這個State對象需要重新Build,重新build后根據已變更的數據來創建新的Widget,但是這個build帶來的消耗是巨大的,它會觸發父類底下每個子widget的build方法。

當某個父widget下,只有部分數據發生改變時,它還是會全局重新刷新,所以這對于個別場景是不適用的。對于網上的一些學習資料中提到的方法,我認為是行之有效的。

1.盡量將需要刷新的statefulWidget放在最小的子節點
2.根布局視圖不要使用statefulWidget
3.將會調用到setState((){}) 的代碼盡可能的和要更新的視圖封裝在一個盡可能小的模塊里。
4.如果一個Widget需要reBuild,那么它的子節點、兄弟節點、兄弟節點的子節點應該盡可能少


3.Widget的生命周期

在iOS里的ViewController中,每個視圖控制器都有自己的生命周期,包含loadView,viewDidLoad等方法一樣,Flutter中的widget也有屬于自己生命周期。

方法 狀態
initState 插入渲染樹時調用,只調用一次
didChangeDependencies state依賴的對象發生變化時調用
build 構建widget時調用
setState 觸發視圖重建
didUpdateWidget 某個組件狀態發生改變時調用,可能會調用多次
deactivate 移除渲染樹時調用
dispose 組件即將銷毀時調用

上面的表格中,羅列了widget生命周期的各個階段。

initState:類似于iOS中ViewController的ViewDidLoad方法,
可以在此做一些初始化的設置,比如為某些狀態變量設置默認值。
didChangeDependencies: 用來專門處理 State 對象依賴關系變化,會在 initState() 調用結束后被調用。
build:構建視圖,創建并返回一個widget。
setState:當狀態數據發生變化時,通過調用這個方法通知 Flutter 更新重構 Widget。
didUpdateWidget:當 Widget 的配置發生變化時,比如,父 Widget 觸發重建(即父 Widget 的狀態發生變化時),熱重載時,系統會調用這個函數。
deactivate:當組件的可見狀態發生變化時,deactivate 函數會被調用,這時 State 會被暫時從視圖樹中移除。當頁面切換時,由于 State 對象在視圖樹中的位置發生了變化,需要先暫時移除后再重新添加,重新觸發組件構建,因為這個函數也會被調用。
dispose:從視圖樹中銷毀時調用。


4.Flutter中視圖的層級關系

關系圖

如上圖所示,Flutter的視圖層級主要包含三個層級:widget,Element和RenderObject。下面我們就按照這三個層級,依次講述其中的知識點。

4.1Widget

首先是widget,按我的理解,它在這三者里應該屬于組織者的角色,通過下面widget的源碼,我們做簡單分析。

abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  /// 創建Widget對應的Element對象,Element對象存儲了Widget的配置信息
  @protected
  Element createElement();

  /// 判斷是否可以更新Widget
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

widget這個class中,主要有以下兩個方法:
createElement:該方法主要是通過widget創建一個對應的Element對象。
canUpdate:該方法主要是判斷widget是否可更新,通過比較新舊widget的runtimeType和key。說到這里就不不提新舊widget之間的重要判斷依據key。

Widget的標識符:Key

Key是所有Widget都擁有的重要屬性,但它并不是默認的,構建某些復雜界面時,我們需要設置widget的key來提升性能。通過Key來比較新舊widget Tree。

4.2 Element

在這三者里,屬于協調者的角色。Element對象會同時持有widget對象和RenderObject對象,相當于是widget和RenderObject之間的橋梁。

它承載了視圖構建的上下文數據,Element與Widget是一對多的關系。由于Element是可變的,所以通過Element將Widget樹的變化做了抽象,可以將真正需要修改的部分同步到RenderObject樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。

我們在代碼中最常看見的BuildContext,其實就是抽象的Element對象。

4.3 RenderObject

RenderObject中包含了所有用來渲染實例Widget的邏輯。它負責layout、painting和hit-testing。它的生成十分耗費性能,所以我們應該盡可能的緩存它。我們與它打交道最多的時候就是在調試布局的時候。

RenderObject 抽象的Widget
layout Column和Row
painting Text和Image
hit-testing GestureDetector

通過上面的敘述,可以總結出三者的基本關系:

視圖由一個個獨立的Element節點構成。組件最終的Layout、渲染都是通過RenderObejct來完成的,從創建到繪制渲染的大體流程是:根據Widget生成Element,然后創建相應的RenderObejct并關聯到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。


Flutter這塊總是寫寫停停的,層級關系這部分還有許多更深的知識點,以后還是要繼續總結學習,回顧舊知識,學習新知識。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377