Flutter之Widget概念

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


1.Widget的劃分

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

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

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

常用的Widget:

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

1.1.StatelessWidget和StatefulWidget

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

statefulWidget:有狀態(tài)的widget,內(nèi)部有保存狀態(tài),當(dāng)狀態(tài)發(fā)生改變,調(diào)用setState()方法,會(huì)觸發(fā)更新UI界面的操作.另外對(duì)于自定義繼承自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ù)傳遞標(biāo)記為final的text。這個(gè)類繼承了StatelessWidget,它包含不可變數(shù)據(jù)。

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

  • 將widget插入樹中時(shí)
  • 當(dāng)widget的父級(jí)更改其配置時(shí)
  • 當(dāng)它依賴的InheritedWidget發(fā)生變化時(shí)
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++;
              });
            },
          )          
        ],
      )
    );
  }
}

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


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

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

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

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

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

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


3.Widget的生命周期

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

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

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

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


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

關(guān)系圖

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

4.1Widget

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

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

  /// 創(chuàng)建Widget對(duì)應(yīng)的Element對(duì)象,Element對(duì)象存儲(chǔ)了Widget的配置信息
  @protected
  Element createElement();

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

widget這個(gè)class中,主要有以下兩個(gè)方法:
createElement:該方法主要是通過(guò)widget創(chuàng)建一個(gè)對(duì)應(yīng)的Element對(duì)象。
canUpdate:該方法主要是判斷widget是否可更新,通過(guò)比較新舊widget的runtimeType和key。說(shuō)到這里就不不提新舊widget之間的重要判斷依據(jù)key。

Widget的標(biāo)識(shí)符:Key

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

4.2 Element

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

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

我們?cè)诖a中最??匆?jiàn)的BuildContext,其實(shí)就是抽象的Element對(duì)象。

4.3 RenderObject

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

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

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

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


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

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

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