自定義Flutter控件

在Flutter開(kāi)發(fā)中,我們會(huì)經(jīng)常和各種控件打交道,它們也能滿足業(yè)務(wù)的大部分需求。但是,我們往往需要將多個(gè)控件組合起來(lái),才能實(shí)現(xiàn)業(yè)務(wù)的需求,而且這樣寫(xiě)出來(lái)的代碼維護(hù)起來(lái)非常困難。因此,我們可以把那些需要多個(gè)控件組合才能實(shí)現(xiàn)的功能自定義化,成為一個(gè)自定義控件,易于維護(hù)。

網(wǎng)絡(luò)圖片

無(wú)狀態(tài)控件

Flutter框架給我們提供了StatelessWidget和StatefulWidget兩個(gè)抽象類,用于自定義控件,首先我們看一下StatelessWidget抽象類。它可以定義一個(gè)不需要可變狀態(tài)的控件,我們可以稱其為“無(wú)狀態(tài)控件”,它通過(guò)構(gòu)建一系列其他控件來(lái)描述用戶界面的一部分,構(gòu)建過(guò)程以遞歸方式執(zhí)行,直到用戶界面的描述完全具體化。

比如,下面是一個(gè)名為“GreenBoard”的StatelessWidget子類的框架,它的里面包括一個(gè)Container控件,中文名稱是“容器”,然后設(shè)置color屬性,也就是背景色為綠色。

class GreenBoard extends StatelessWidget {
  const GreenBoard({ Key key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: const Color(0xFF2DBD3A)
    );
  }
}

上面代碼中,應(yīng)用了《Dart入門(mén)—類與方法》的知識(shí),大家可以去簡(jiǎn)單了解下。另外,我們覆蓋了build這個(gè)抽象方法,該方法用于描述由此控件所實(shí)現(xiàn)的那一部分用戶界面。其中,抽象類BuildContext是該控件在控件樹(shù)中的位置句柄。

通常情況下,控件的構(gòu)造函數(shù)參數(shù)不止一個(gè),每個(gè)參數(shù)對(duì)應(yīng)一個(gè)final修飾的屬性,修改上一個(gè)例子,使其成為一個(gè)可以設(shè)置背景顏色和子控件的通用控件。

class Board extends StatelessWidget {
  const Board({
    Key key,
    this.color: const Color(0xFF2DBD3A),
    this.child,
  }) : super(key: key);

  final Color color;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color, 
      child: child,
    );
  }
}

按照Flutter框架的語(yǔ)法規(guī)范,控件的構(gòu)造函數(shù)只能使用命名參數(shù),命名參數(shù)可以使用@required注解為必需參數(shù)。另外語(yǔ)法規(guī)范還規(guī)定了,第一個(gè)參數(shù)是key,最后一個(gè)參數(shù)是childchildren或其他類似參數(shù)。

如果一個(gè)無(wú)狀態(tài)控件的父控件會(huì)定期更改控件的配置,或者它依賴于頻繁更改的繼承控件,那么優(yōu)化build方法的性能,以保持流暢的渲染性能是非常重要的。有以下做法可以用于減少重新構(gòu)建無(wú)狀態(tài)控件的影響:

  • 盡可能減少build方法傳遞創(chuàng)建的節(jié)點(diǎn)數(shù)量及其創(chuàng)建的任何控件。例如,我們可以考慮只使用Align(對(duì)齊控件)或CustomSingleChildLayout(自定義單個(gè)子控件布局控件),而不是精心安排Row(行布局控件)、Column(列布局控件)、Padding(填充控件)和SizedBox(指定大小的框控件)來(lái)定位單個(gè)子控件。想繪制正確的圖形效果時(shí),考慮一下使用CustomPaint(畫(huà)布控件),而不是復(fù)雜的多個(gè)Container(容器)分層和Decoration(裝飾)。
  • 盡可能使用const修飾控件,并為控件提供一個(gè)const構(gòu)造函數(shù),以便控件的調(diào)用方法也可以這樣做。

有狀態(tài)控件

在了解完如何使用StatelessWidget定義一個(gè)無(wú)狀態(tài)控件后,我們學(xué)習(xí)如何使用StatefulWidget定義一個(gè)具有可變狀態(tài)的控件,我們可以稱其為“有狀態(tài)控件”。首先要搞清楚的是,狀態(tài)是什么?狀態(tài)是在構(gòu)建控件時(shí)可以同步讀取的信息,并且在控件的生命周期內(nèi)可以改變,控件的使用者應(yīng)該在狀態(tài)發(fā)生變化時(shí)使用State.setState方法及時(shí)通知框架。

StatefulWidget的實(shí)例本身是不可變的,我們需要將其可變狀態(tài)存儲(chǔ)在由createState方法創(chuàng)建的單獨(dú)的State對(duì)象中,或者存儲(chǔ)在該State所訂閱的對(duì)象中。例如,我們將之前名為“GreenBoard”的StatelessWidget子類改造成StatefulWidget的子類框架。

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

  @override
  _GreenBoardState createState() => new _GreenBoardState();
}

class _GreenBoardState extends State<GreenBoard> {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: const Color(0xFF2DBD3A)
    );
  }
}

只要我們調(diào)用了一個(gè)StatefulWidget,框架就會(huì)調(diào)用createState,這意味著,在控件樹(shù)中,可能有多個(gè)不同位置的State對(duì)象與同一個(gè)StatefulWidget關(guān)聯(lián)。同樣的,如果一個(gè)StatefulWidget被我們從樹(shù)中移除,并且再次被我們插入到樹(shù)中,框架將再次調(diào)用createState來(lái)創(chuàng)建一個(gè)新的State對(duì)象,簡(jiǎn)化了State對(duì)象的生命周期。

我們?cè)賹⒅懊麨椤癇oard”的StatelessWidget子類改造成StatefulWidget的子類框架。除了可以設(shè)置背景顏色和子控件,還有一個(gè)可以被調(diào)用的,用來(lái)改變它的內(nèi)部狀態(tài)的方法。

class Board extends StatefulWidget {
  const Board({
    Key key,
    this.color: const Color(0xFF2DBD3A),
    this.child,
  }) : super(key: key);

  final Color color;
  final Widget child;

  _BoardState createState() => new _BoardState();
}

class _BoardState extends State<Board> {
  double _size = 1.0;

  void grow() {
    setState(() { _size += 0.1; });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: widget.color,
      transform: new Matrix4.diagonal3Values(_size, _size, 1.0),
      child: widget.child,
    );
  }
}

StatefulWidget有兩種不同的類型:

  • 第一種是在State.initState中分配資源并將其置于State.dispose中,而且不依賴于InheritedWidget或調(diào)用State.setState。這樣的控件通常在應(yīng)用程序或頁(yè)面的根部使用,并通過(guò)ChangeNotifier、Stream或其他這樣的對(duì)象與子控件通信。遵循這種模式的有狀態(tài)控件相對(duì)便宜(在CPU和GPU周期方面),因?yàn)樗鼈兪且淮螛?gòu)建的,而且不會(huì)更新。因此,它們可以實(shí)現(xiàn)一些非常復(fù)雜的build方法。
  • 第二種是使用State.setState或依賴于InheritedWidget的控件。這些控件通常會(huì)在應(yīng)用程序的生命周期中重新構(gòu)建很多次,因此降低重新構(gòu)建這種控件的影響至關(guān)重要。實(shí)際上,它們也可以使用State.initState或State.didChangeDependencies來(lái)分配資源,但重點(diǎn)是它們要重新構(gòu)建。

有以下做法可以用于減少重新構(gòu)建有狀態(tài)控件的影響:

  • 把狀態(tài)推到樹(shù)葉上。例如,我們的頁(yè)面上有一個(gè)嘀嗒嘀嗒的時(shí)鐘,此時(shí)不應(yīng)該將狀態(tài)置于頁(yè)面的頂部,因?yàn)檫@樣的話,每當(dāng)時(shí)鐘嘀嗒時(shí),整個(gè)頁(yè)面都會(huì)重新構(gòu)建。我們應(yīng)該創(chuàng)建一個(gè)專用的時(shí)鐘控件,這樣只會(huì)更新它自己。
  • 盡可能減少build方法傳遞創(chuàng)建的節(jié)點(diǎn)數(shù)量及其創(chuàng)建的任何控件。理想情況下,有狀態(tài)的控件只會(huì)創(chuàng)建一個(gè)控件,而這個(gè)控件將是一個(gè)RenderObjectWidget。很顯然,在實(shí)際開(kāi)發(fā)中,我們不一定能做到這一點(diǎn),但控件越接近這個(gè)理想狀態(tài),效率就越高。
  • 如果子樹(shù)不更改,則緩存表示該子樹(shù)的控件,并在每次使用該子樹(shù)時(shí)重新使用它。重用控件的效率要比創(chuàng)建新的控件要高效得多,將有狀態(tài)的部分分解成一個(gè)帶有子參數(shù)的控件是這樣做的常見(jiàn)方法。
  • 盡可能使用const修飾控件,這相當(dāng)于緩存一個(gè)控件,并重新使用它。

關(guān)于有狀態(tài)與無(wú)狀態(tài)的選擇

如果自定義的控件可以與用戶進(jìn)行交互,比如通過(guò)鍵盤(pán)輸入內(nèi)容、通過(guò)滑動(dòng)屏幕移動(dòng)滑塊、點(diǎn)擊時(shí)改變狀態(tài),又或者是隨著時(shí)間的推移而變化,比如數(shù)據(jù)Feed會(huì)更新?tīng)顟B(tài)。這時(shí)我們應(yīng)該選擇使用StatefulWidget創(chuàng)建一個(gè)有狀態(tài)控件。

如果自定義的控件僅依賴于對(duì)象本身的配置信息,僅僅是用于展示給定的信息。那我們應(yīng)該選擇使用StatelessWidget創(chuàng)建一個(gè)無(wú)狀態(tài)控件。

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 無(wú)狀態(tài)控件 Flutter框架給我們提供了StatelessWidget和StatefulWidget兩個(gè)抽象類,...
    雪納瑞的哈士奇閱讀 294評(píng)論 0 0
  • 1.前言 本文涵蓋了Widget,State,BuildContext,InheritedWidget等術(shù)語(yǔ)的相關(guān)...
    巧巧的二表哥閱讀 2,709評(píng)論 0 5
  • Flutter中Widget,State和BuildContext的概念是每個(gè)Flutter開(kāi)發(fā)人員需要完全理解的...
    DramaScript閱讀 5,913評(píng)論 0 1
  • 本文主要介紹了Flutter布局相關(guān)的內(nèi)容,對(duì)相關(guān)知識(shí)點(diǎn)進(jìn)行了梳理,并從實(shí)際例子觸發(fā),進(jìn)一步講解該如何去進(jìn)行布局。...
    Q吹個(gè)大氣球Q閱讀 10,187評(píng)論 6 51
  • 轉(zhuǎn)自 Q吹個(gè)大氣球Q 本文主要介紹了Flutter布局相關(guān)的內(nèi)容,對(duì)相關(guān)知識(shí)點(diǎn)進(jìn)行了梳理,并從實(shí)際例子觸發(fā),進(jìn)一步...
    chilim閱讀 1,999評(píng)論 0 17

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