Flutter之Widget概念

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


1.Widget的劃分

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

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

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

常用的Widget:

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

1.1.StatelessWidget和StatefulWidget

statelessWidget:表示無狀態(tài)的widget,內(nèi)部沒有保存狀態(tài),UI界面一經(jīng)創(chuàng)建不會發(fā)生改變.

statefulWidget:有狀態(tài)的widget,內(nèi)部有保存狀態(tài),當狀態(tài)發(fā)生改變,調(diào)用setState()方法,會觸發(fā)更新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類的構(gòu)造函數(shù)傳遞標記為final的text。這個類繼承了StatelessWidget,它包含不可變數(shù)據(jù)。

statelessWidget的build方法通常只會在以下三種情況調(diào)用:

  • 將widget插入樹中時
  • 當widget的父級更改其配置時
  • 當它依賴的InheritedWidget發(fā)生變化時
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()方法。此方法創(chuàng)建管理widget狀態(tài)的狀態(tài)對象_HomePageState 。


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

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

首先需要判斷widget的狀態(tài),簡單點,沒有數(shù)據(jù)驅(qū)動,信息不可變的,都是statelessWidget,F(xiàn)lutter本身也告訴你,優(yōu)先使用StatelessWidget。

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

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

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


3.Widget的生命周期

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

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

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

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


4.Flutter中視圖的層級關(guān)系

關(guān)系圖

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

4.1Widget

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

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

  /// 創(chuàng)建Widget對應(yīng)的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創(chuàng)建一個對應(yīng)的Element對象。
canUpdate:該方法主要是判斷widget是否可更新,通過比較新舊widget的runtimeType和key。說到這里就不不提新舊widget之間的重要判斷依據(jù)key。

Widget的標識符:Key

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

4.2 Element

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

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

我們在代碼中最??匆姷?strong>BuildContext,其實就是抽象的Element對象。

4.3 RenderObject

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

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

通過上面的敘述,可以總結(jié)出三者的基本關(guān)系:

視圖由一個個獨立的Element節(jié)點構(gòu)成。組件最終的Layout、渲染都是通過RenderObejct來完成的,從創(chuàng)建到繪制渲染的大體流程是:根據(jù)Widget生成Element,然后創(chuàng)建相應(yīng)的RenderObejct并關(guān)聯(lián)到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。


Flutter這塊總是寫寫停停的,層級關(guān)系這部分還有許多更深的知識點,以后還是要繼續(xù)總結(jié)學(xué)習(xí),回顧舊知識,學(xué)習(xí)新知識。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容