Flutter了解之入門篇1(Widget)

目錄
  1. 模版示例(計(jì)數(shù)器)
  2. Widget
  3. 內(nèi)置組件庫、自定義組件

1. 模版示例(計(jì)數(shù)器)

AndroidStudio創(chuàng)建的Flutter應(yīng)用 默認(rèn)是一個(gè)計(jì)數(shù)器示例(每點(diǎn)擊一次右下角的加號(hào)按鈕,屏幕中央的數(shù)字就會(huì)加1)。


  1. 程序入口(lib目錄下的main.dart文件)
// import:用來導(dǎo)入文件。
import 'package:flutter/material.dart'; // 導(dǎo)入Material UI組件庫。
// 應(yīng)用入口,啟動(dòng)Flutter應(yīng)用后會(huì)調(diào)用
void main() {
  // runApp方法接受一個(gè)根Widget參數(shù)(Flutter框架會(huì)強(qiáng)制根widget覆蓋整個(gè)屏幕)。
  runApp(MyApp());
}
// 根widget
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // MaterialApp:Material庫提供的App框架組件,可用來設(shè)置應(yīng)用的名稱、主題、語言、首頁及路由列表等,創(chuàng)建了一些有用的widget(如:Navigator)。
    // 是否使用MaterialApp是可選的,但推薦使用。使用了MaterialApp則必須使用Scaffold。
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // 設(shè)置主題色為藍(lán)色(會(huì)影響:導(dǎo)航欄顏色、底部Tab的選中色)
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      // 首頁
      home: MyHomePage(title: 'Hello Home Page'),
    );
  }
}
// 首頁,繼承自StatefulWidget類(有狀態(tài)的組件)
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  // _MyHomePageState類是MyHomePage類對(duì)應(yīng)的狀態(tài)類。
  // 和StatelessWidget類不同, StatefulWidget類中并沒有build方法(被挪到了State中)。
  _MyHomePageState createState() => _MyHomePageState();
}
// State狀態(tài)類
class _MyHomePageState extends State<MyHomePage> {
  // 該組件的狀態(tài)
  // 定義一個(gè)_counter狀態(tài)用于記錄按鈕的點(diǎn)擊次數(shù)
  int _counter = 0;
  // 點(diǎn)擊按鈕后調(diào)用,更新狀態(tài)繼而更新UI
  void _incrementCounter() {
    // setState方法的作用是通知Flutter框架,有狀態(tài)發(fā)生了改變,F(xiàn)lutter框架收到通知后,會(huì)執(zhí)行build方法來根據(jù)新的狀態(tài)重新構(gòu)建界面。
    setState(() {
      _counter++;
    });
  }
  // 組件的主要工作:提供一個(gè)build()方法返回構(gòu)建的UI界面(通常通過組合其它基礎(chǔ)widget來構(gòu)建UI)。
  // 1. 當(dāng)MyHomePage第一次創(chuàng)建時(shí),_MyHomePageState類會(huì)被創(chuàng)建,當(dāng)初始化完成后,F(xiàn)lutter框架會(huì)調(diào)用Widget的build()方法來構(gòu)建widget樹,最終會(huì)將widget樹渲染到設(shè)備屏幕上。
  // 2. 調(diào)用setState后會(huì)重新調(diào)用。
  @override
  Widget build(BuildContext context) {
    // Scaffold 是Material庫中提供的頁面腳手架(提供了導(dǎo)航欄、body、懸浮按鈕等屬性)。
    return Scaffold(
      appBar: AppBar(  // 導(dǎo)航欄
        title: Text(widget.title),
      ),
      // Center組件會(huì)將其子組件樹對(duì)齊到屏幕中心。Column組件會(huì)將其子組件沿屏幕垂直方向依次排列。
      // Center子組件是一個(gè)Column組件,Column子組件是兩個(gè)Text組件。
      body: Center(  
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      // 頁面右下角的帶“+”的懸浮按鈕,onPressed屬性接受一個(gè)點(diǎn)擊后的回調(diào)處理函數(shù)。
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // 后面的逗號(hào)便于后期自動(dòng)格式化排版。
    );
  }
}
  1. 程序配置(根目錄下的pubspec.yaml文件)
#項(xiàng)目名
name: flutter_textfield_app
#項(xiàng)目描述
description: A new Flutter project.
#避免通過pub publish發(fā)布到pub.dev上。表明這是個(gè)私有項(xiàng)目。
publish_to: 'none' 

#版本名和版本號(hào)通過+分開
#通過指定 --build-name 和 --build-number 來設(shè)置版本名、版本號(hào)
#在Android中,build name用作versionName,而build number用作versionCode。
#在iOS中,內(nèi)部版本名用作CFBundleShortVersionString,而內(nèi)部版本號(hào)用作CfBundLeverVersion。
version: 1.0.0+1

#FlutterSDK版本
environment:
  sdk: ">=2.7.0 <3.0.0"

#依賴包(生產(chǎn)環(huán)境)
dependencies:
  flutter:
    sdk: flutter

#依賴包(開發(fā)環(huán)境)
dev_dependencies:
  flutter_test:
    sdk: flutter

#指定圖片、字體等資源
flutter:
  #包含了Material Icons
  uses-material-design: true

2. Widget

Flutter中萬物皆為Widget:幾乎所有的對(duì)象(UI組件、功能組件、布局組件)都是一個(gè)Widget。

1. widget的主要工作是實(shí)現(xiàn)build方法(構(gòu)建自身UI)。
2. 一個(gè)widget通常由一些較低級(jí)別(層級(jí))widget組成,F(xiàn)lutter框架將依次構(gòu)建這些widget,直到構(gòu)建到最底層的widget(RenderObject)。
3. Widget樹實(shí)際上是一個(gè)配置樹,Element樹才是真正的UI渲染樹(二者存在對(duì)應(yīng)關(guān)系)。
  Widget的功能是“描述一個(gè)UI元素的配置數(shù)據(jù)”,即Widget并不是表示最終繪制在設(shè)備屏幕上的顯示元素(Element才是),它只是描述顯示元素的配置數(shù)據(jù)。
4.有狀態(tài)組件(Stateful widget) 和無狀態(tài)組件(Stateless widget)的區(qū)別:
  1. Stateful widget可以擁有狀態(tài),這些狀態(tài)在widget生命周期中是可以變的,而Stateless widget是不可變的。
  2. Stateful widget至少由兩個(gè)類組成:
    一個(gè)StatefulWidget類;一個(gè) State類。
    StatefulWidget類本身是不可變的,但是State類中持有的狀態(tài)在widget生命周期中可能會(huì)發(fā)生變化。
/*
build方法放在State類中,而不是放在StatefulWidget中的原因。
如果將build()方法放在StatefulWidget中:
  1. 狀態(tài)訪問不便。
    build方法構(gòu)建UI時(shí)讀取state(State的state就需要對(duì)外公開,不方便、不安全)。
    state改變時(shí)需要調(diào)用build方法更新UI。
  2. 繼承StatefulWidget不便。例如:
    Flutter中有一個(gè)動(dòng)畫widget的基類AnimatedWidget,它繼承自StatefulWidget類。AnimatedWidget中有一個(gè)抽象方法build(BuildContext context),繼承自AnimatedWidget的動(dòng)畫widget都要實(shí)現(xiàn)這個(gè)build方法。
    現(xiàn)在設(shè)想一下,如果StatefulWidget 類中已經(jīng)有了一個(gè)build方法,正如上面所述,此時(shí)build方法需要接收一個(gè)state對(duì)象,這就意味著AnimatedWidget必須將自己的State對(duì)象(記為_animatedWidgetState)提供給其子類,因?yàn)樽宇愋枰谄鋌uild方法中調(diào)用父類的build方法。
    這樣很顯然是不合理的,因?yàn)椋篈nimatedWidget的狀態(tài)對(duì)象是AnimatedWidget內(nèi)部實(shí)現(xiàn)細(xì)節(jié),不應(yīng)該暴露給外部;如果要將父類狀態(tài)暴露給子類,那么必須得有一種傳遞機(jī)制,而做這一套傳遞機(jī)制是無意義的,因?yàn)楦缸宇愔g狀態(tài)的傳遞和子類本身邏輯是無關(guān)的。
*/
Flutter組件庫中的很多基礎(chǔ)組件(Text 、Column、Align等,好比積木)是通過自定義RenderObject來實(shí)現(xiàn)的,而不是通過StatelessWidget 和 StatefulWidget(組合其他組件,本身沒有對(duì)應(yīng)的RenderObject,負(fù)責(zé)塔積木)來實(shí)現(xiàn)的。
  1. Widget 抽象類,繼承自DiagnosticableTree(診斷樹,主要作用是提供調(diào)試信息)

不會(huì)直接繼承Widget類來實(shí)現(xiàn)新組件,而是通過繼承StatelessWidget或StatefulWidget(繼承自Widget類)。

Widget定義如下:

// @immutable:限制Widget沒有可變狀態(tài),所有屬性都必須是final。這是因?yàn)?,F(xiàn)lutter中如果屬性發(fā)生變化則會(huì)重新構(gòu)建Widget樹,即重新創(chuàng)建新的Widget實(shí)例來替換舊的Widget實(shí)例,所以允許Widget的屬性變化是沒有意義的,因?yàn)橐坏¦idget的屬性變了Widget自己就會(huì)被替換。
@immutable
abstract class Widget extends DiagnosticableTree {
  // key:決定是否在下一次build時(shí)復(fù)用舊的Element,決定的條件在canUpdate()方法中
  const Widget({ this.key });
  final Key key;

  // Flutter框架在構(gòu)建UI樹時(shí),會(huì)先調(diào)用此方法(開發(fā)過程中基本不會(huì)主動(dòng)調(diào)用)生成對(duì)應(yīng)節(jié)點(diǎn)的Element對(duì)象。
  @protected
  Element createElement();

  // Widget的字符串描述
  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }
  // 覆寫父類的方法,主要是設(shè)置診斷樹的一些特性
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;
  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  // 是否用新Widget對(duì)象去更新舊UI樹上所對(duì)應(yīng)的Element對(duì)象的配置;只要newWidget與oldWidget的runtimeType和key同時(shí)相等時(shí)就會(huì)用newWidget去更新Element對(duì)象的配置,否則就會(huì)創(chuàng)建新的Element。key都為null也代表key相等。
  // 多數(shù)情況下都返回true。
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  // 返回1是StatefulWidget,返回2是StatelessWidget,返回0是其他widget
  static int _debugConcreteSubtype(Widget widget) {
    return widget is StatefulWidget ? 1 :
           widget is StatelessWidget ? 2 :
           0;
    }
}
  1. StatelessWidget 抽象類,繼承自Widget(重寫了createElement方法)無狀態(tài)組件

用于不需要維護(hù)狀態(tài)的場(chǎng)景。無狀態(tài)組件不會(huì)發(fā)生狀態(tài)改變(即沒有可變狀態(tài)),所有屬性都是final類型(通常在父組件中調(diào)用無狀態(tài)組件的構(gòu)造函數(shù)時(shí)賦值)。

StatelessWidget定義如下:

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);
  // 一般子類不重寫該方法。StatelessElement 間接繼承自Element。
  @override
  StatelessElement createElement() => StatelessElement(this);

  // context(BuildContext類的實(shí)例,實(shí)際就是element對(duì)象)表示當(dāng)前widget在widget樹中的上下文,每一個(gè)widget都會(huì)對(duì)應(yīng)一個(gè)context對(duì)象(因?yàn)槊恳粋€(gè)widget都是widget樹上的一個(gè)節(jié)點(diǎn))。context是當(dāng)前widget在widget樹中位置中執(zhí)行”相關(guān)操作“的一個(gè)句柄,比如它提供了從當(dāng)前widget開始向上遍歷widget樹以及按照widget類型查找父級(jí)widget的方法。
  // 當(dāng)widget插入到樹中時(shí)會(huì)調(diào)用,調(diào)用setState后會(huì)調(diào)用build來重新構(gòu)建UI。
  // 通常在build方法中通過嵌套其它Widget來構(gòu)建UI。
  @protected
  Widget build(BuildContext context);
}

示例(一個(gè)灰色背景字符串的widget)

class HelloWidget extends StatelessWidget {
  final String text;
  final Color backgroundColor;
   // widget的構(gòu)造函數(shù)參數(shù)應(yīng)使用命名參數(shù)(規(guī)范-語義更清晰),命名參數(shù)中的必要參數(shù)要添加@required標(biāo)注(這樣有利于靜態(tài)代碼分析器進(jìn)行檢查)。
  // 第一個(gè)參數(shù)通常應(yīng)該是Key。如果需要接收子Widget,那么child或children參數(shù)通常應(yīng)被放在參數(shù)列表的最后。
  // Widget的屬性應(yīng)聲明為final,防止被意外改變。
  const HelloWidget({
    Key key,  
    @required this.text,
    this.backgroundColor:Colors.grey,
  }):super(key:key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}
在其他widget的build中使用:
Widget build(BuildContext context) {
  return HelloWidget(text: "hello world");
}

示例2(在子樹中獲取父級(jí)widget)

class ContextRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Context測(cè)試"),
      ),
      body: Container(
        child: Builder(builder: (context) {
          // 在Widget樹中向上查找最近的父級(jí)Scaffold widget
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}
  1. StatefulWidget 抽象類(繼承自Widget,重寫了createElement方法,添加了createState方法)有狀態(tài)組件
StatefulWidget定義如下:

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);
  // 一般子類不重寫該方法,StatefulElement間接繼承自Element類。
  @override
  StatefulElement createElement() => StatefulElement(this);

  // 子類需要重寫該方法,返回關(guān)聯(lián)的state狀態(tài)。
  @protected
  State createState();
}

1. 在繼承StatefulWidget重寫其方法時(shí),對(duì)于包含@mustCallSuper標(biāo)注的父類方法,都要在子類方法中先調(diào)用父類方法。
2. 在Flutter中,事件流是“向上”傳遞的,而狀態(tài)流是“向下”傳遞的。即子widget到父widget是通過事件通信,父向子共享狀態(tài)。

State類

一個(gè)StatefulWidget類對(duì)應(yīng)一個(gè)State類,State表示與其對(duì)應(yīng)的StatefulWidget要維護(hù)的狀態(tài)。

State中的狀態(tài)信息:
    1. 在widget構(gòu)建時(shí)可以被讀取。
    2. 可以改變,改變后可以手動(dòng)調(diào)用其setState()方法通知Flutter框架 狀態(tài)發(fā)生改變,F(xiàn)lutter框架會(huì)重新調(diào)用其build方法重新構(gòu)建widget樹來更新UI。

State中兩個(gè)常用屬性:
    1. widget(與該State關(guān)聯(lián)的widget,由Flutter框架動(dòng)態(tài)設(shè)置)。
      State實(shí)例只會(huì)在第一次插入到樹中時(shí)被創(chuàng)建。
      在重新構(gòu)建時(shí),如果widget被修改,F(xiàn)lutter框架會(huì)動(dòng)態(tài)設(shè)置State.widget為新的widget實(shí)例。
    2. context。
      StatefulWidget對(duì)應(yīng)的BuildContext,作用同StatelessWidget的BuildContext。
/*
調(diào)用setState刷新StatefulWidget的官方使用建議:

  1. 盡可能將組件樹的狀態(tài)維護(hù)往下推到葉子節(jié)點(diǎn)。
    狀態(tài)維護(hù)層級(jí)越高,意味著重建的組件樹越大。
  2. 最小化StatefulWidget的State中build方法構(gòu)建組件的數(shù)量。
  3. 如果子組件樹在整個(gè)生命周期都不改變的話,那么應(yīng)該考慮將該子組件樹抽離緩存起來重復(fù)利用。
  4. 盡可能地使用const修飾子組件構(gòu)造方法。
  5. 盡可能避免更改子組件樹的層級(jí)或子組件樹中的組件類型。任何更改子組件樹深度的操作都會(huì)需要重新構(gòu)建、重新布局、重新繪制整個(gè)子組件樹。IgnorePointer包裹。
  6. 假設(shè)不得不更改子組件樹的層級(jí),那么應(yīng)該考慮將子組件樹中不變的部分使用 GlobalKey 使得這部分在整個(gè)StatefulWidget的生命周期都保持一致。如果不方便使用GlobalKey的話,那么可以考慮使用KeyedSubtree組件來應(yīng)用GlobalKey。
  7. 如果StatefulWidget中有些屬性是不變的話,那么 這些屬性的定義應(yīng)該優(yōu)先放在 `Widget` 的定義中,并聲明為final,而不是State中,這樣可以減少State需要維護(hù)的數(shù)據(jù)。
*/
StatefulWidget的生命周期

示例(了解StatefulWidget的生命周期)

實(shí)現(xiàn)一個(gè)計(jì)數(shù)器widget,點(diǎn)擊它可以使計(jì)數(shù)器加1,由于要保存計(jì)數(shù)器的數(shù)值狀態(tài),所以應(yīng)繼承StatefulWidget
class CounterWidget extends StatefulWidget {
  const CounterWidget({
    Key key,
    this.initValue: 0
  });
  final int initValue;
  @override
  _CounterWidgetState createState() => new _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;
  // Widget第一次插入到Widget樹時(shí)會(huì)被調(diào)用。
  // 對(duì)于每一個(gè)State對(duì)象,F(xiàn)lutter框架只會(huì)調(diào)用一次該回調(diào)。通常在該方法中做一些一次性操作(如:請(qǐng)求網(wǎng)路、狀態(tài)初始化、訂閱子樹的事件通知等)。
  // 不能在該回調(diào)中調(diào)用BuildContext.dependOnInheritedWidgetOfExactType(該方法用于在Widget樹上獲取離當(dāng)前widget最近的一個(gè)父級(jí)InheritFromWidget),原因是在初始化完成后,Widget樹中的InheritFromWidget可能會(huì)發(fā)生變化,所以正確的做法應(yīng)該在build方法或didChangeDependencies方法中調(diào)用它。
  @override
  void initState() {
    super.initState();
    _counter=widget.initValue;  // 初始化狀態(tài)  
    print("initState");
  }
/*
用于構(gòu)建Widget子樹的,會(huì)在如下場(chǎng)景被調(diào)用:
    在調(diào)用initState()之后。
    在調(diào)用didUpdateWidget()之后。
    在調(diào)用setState()之后。
    在調(diào)用didChangeDependencies()之后。
    在State對(duì)象從樹中一個(gè)位置移除后(會(huì)調(diào)用deactivate)又重新插入到樹的其它位置之后。
*/
  // 構(gòu)建Widget
  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          onPressed:()=>setState(()=> ++_counter, // 點(diǎn)擊后計(jì)數(shù)器自增
          ),
        ),
      ),
    );
  }
  // widget發(fā)生改變需要重新構(gòu)建時(shí),F(xiàn)lutter框架會(huì)調(diào)用Widget.canUpdate來檢測(cè)Widget樹中同一位置的新舊節(jié)點(diǎn)來決定是否需要更新,如果返回true(會(huì)在新舊widget的key和runtimeType同時(shí)相等時(shí)返回true)則會(huì)調(diào)用該方法。
  // 該方法調(diào)用之后會(huì)調(diào)用build方法。
  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }
  // (State對(duì)象)從樹中被移除時(shí)會(huì)調(diào)用。有時(shí)Flutter框架會(huì)將State對(duì)象重新插到樹中,如包含此State對(duì)象的子樹在樹的一個(gè)位置移動(dòng)到另一個(gè)位置時(shí)(可以通過GlobalKey來實(shí)現(xiàn))。
  // 如果移除后沒有重新插入到樹中則緊接著會(huì)調(diào)用dispose()方法。
  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }
  // (State對(duì)象)從樹中被永久移除時(shí)調(diào)用。
  // 通常在該方法中釋放資源(定時(shí)器、未完成的動(dòng)畫)。
  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }
  // 專門為了開發(fā)調(diào)試而提供,在熱重載時(shí)會(huì)被調(diào)用。該方法調(diào)用之后會(huì)調(diào)用didUpdateWidget方法。
  // 在Release模式下永遠(yuǎn)不會(huì)被調(diào)用。
  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }
  // initState方法調(diào)用后會(huì)調(diào)用該方法。該方法調(diào)用之后會(huì)調(diào)用build方法。
  // 會(huì)在依賴(是否使用了父輩widget中InheritedWidget的數(shù)據(jù))改變后調(diào)用該方法。比如當(dāng)主題、locale(語言)改變時(shí),依賴其的子widget會(huì)調(diào)用該方法。
  //  子widget一般很少重寫該方法,因?yàn)樵谝蕾嚫淖兒驠lutter Framework也會(huì)調(diào)用build()方法來更新UI。但如果需要在依賴改變后執(zhí)行一些昂貴的操作(網(wǎng)絡(luò)請(qǐng)求),則最好在該方法中執(zhí)行,可以避免每次build()都執(zhí)行這些昂貴操作。
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}
// 在其他組件的build方法中使用自定義的CounterWidget
Widget build(BuildContext context) {
  return CounterWidget();
}
終端輸出
flutter: initState
flutter: didChangeDependencies
flutter: build
保存,終端輸出
flutter: reassemble
flutter: didUpdateWidget
flutter: build
將return CounterWidget();改為return Text("xxx");并保存,終端輸出
flutter: reassemble
flutter: deactive
flutter: dispose

在Widget樹中獲取State對(duì)象

由于StatefulWidget的的具體邏輯都在其State中,所以很多時(shí)候,需要獲取StatefulWidget對(duì)應(yīng)的State對(duì)象來調(diào)用一些方法。比如Scaffold組件對(duì)應(yīng)的狀態(tài)類ScaffoldState中就定義了打開SnackBar(路由頁底部提示條)的方法。

在子widget樹中獲取父輩級(jí)StatefulWidget的State對(duì)象:
  方法1. 通過Context獲取
    context對(duì)象有一個(gè)findAncestorStateOfType()方法,該方法可以從當(dāng)前節(jié)點(diǎn)沿著widget樹向上查找指定類型的StatefulWidget對(duì)應(yīng)的State對(duì)象。
  方法2. 通過GlobalKey獲取
    第一步:給目標(biāo)StatefulWidget添加GlobalKey
    // 定義一個(gè)globalKey, 由于GlobalKey要保持全局唯一性,使用使用靜態(tài)變量存儲(chǔ)
    static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
    Scaffold(
        key: _globalKey , //設(shè)置key
    )
    第二步:通過GlobalKey來獲取State對(duì)象
    _globalKey.currentState.openDrawer()
/*
如果一個(gè)widget設(shè)置了GlobalKey,那么便可以通過
  globalKey.currentWidget 獲得該widget對(duì)象
  globalKey.currentElement 獲得該widget對(duì)象對(duì)應(yīng)的element對(duì)象
  globalKey.currentState 獲得該widget對(duì)象對(duì)應(yīng)的state對(duì)象(如果當(dāng)前widget是StatefulWidget)。

使用GlobalKey開銷較大,如果有其他可選方案,應(yīng)盡量避免使用它。
*/
示例(通過Context獲取State對(duì)象)

Scaffold(
  appBar: AppBar(
    title: Text("子樹中獲取State對(duì)象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父級(jí)最近的Scaffold對(duì)應(yīng)的ScaffoldState對(duì)象,調(diào)用state的showSnackBar方法來彈出SnackBar
          ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>();
          _state.showSnackBar(
            SnackBar(
              content: Text("我是SnackBar"),
            ),
          );
        },
        child: Text("顯示SnackBar"),
      );
    }),
  ),
);
如果StatefulWidget的狀態(tài)是私有的(不應(yīng)該向外部暴露),那么代碼中就不應(yīng)該去直接獲取其State對(duì)象;如果StatefulWidget的狀態(tài)是希望暴露出的,則可以去直接獲取其State對(duì)象。
但通過context.findAncestorStateOfType獲取StatefulWidget的狀態(tài)的方法是通用的(并不能看出StatefulWidget的狀態(tài)是否私有),所以在Flutter開發(fā)中便有了一個(gè)默認(rèn)的約定【如果StatefulWidget的狀態(tài)是希望暴露出的,應(yīng)提供一個(gè)of靜態(tài)方法來獲取其State對(duì)象;如果State不希望暴露,則不提供of方法】。
上面示例中的Scaffold就提供了一個(gè)of靜態(tài)方法來直接獲取ScaffoldState:
ScaffoldState _state=Scaffold.of(context);   
_state.showSnackBar(
  SnackBar(
    content: Text("我是SnackBar"),
  ),
);

setState方法(避免使用該方法來更新UI,除非組件樹中的組件很少)

setState方法定義在State<T extends StatefulWidget> with Diagnosticable這個(gè)類中。
  首先使用assert斷言進(jìn)行了如下限制:
    1. 傳給setState的回調(diào)方法不能為空。
    2. 不能在dispose后調(diào)用setState(已經(jīng)從組件樹中移除)。
      可能發(fā)生在定時(shí)器、動(dòng)畫或異步回調(diào)的過程中,會(huì)導(dǎo)致內(nèi)存泄露。
    3. 不能在created階段和!mounted未裝載階段調(diào)用setState(即不能在構(gòu)造函數(shù)里調(diào)用 setState)。
      通常應(yīng)在 initState 之后調(diào)用 setState。
    4. setState的回調(diào)方法不能返回Future對(duì)象(即不能在setState中執(zhí)行異步操作)。
  最后調(diào)用了_element!.markNeedsBuild(); 

繼續(xù)查看源碼發(fā)現(xiàn):
  獲取BuildContext的方法,返回的也是_element(StatefulElement類型)。
  StatefulElement繼承自ComponentElement,ComponentElement繼承自Element,Element繼承自DiagnosticableTree并實(shí)現(xiàn)了BuildContext接口。markNeedsBuild方法是在Element類中定義的。
Element類詳情如下:

element元素的生命周期
Element中關(guān)鍵的屬性和方法:
  1. _parent屬性
    父元素(可為空)。
  2. _depth屬性
    元素在組件樹中的層級(jí),根節(jié)點(diǎn)的該值必須大于0。
  3. _widget屬性
    元素對(duì)應(yīng)的widget。
  4. _owner屬性
    管理元素生命周期的對(duì)象。
  5. _lifecycleState屬性
    生命周期狀態(tài)(默認(rèn)是initial狀態(tài))。
  6. 獲取renderObject的get方法
    會(huì)遞歸調(diào)用返回元素及其子元素中需要渲染的對(duì)象(子元素是RenderObjectElement對(duì)象)。
  7. _sort方法
    比較兩個(gè)Element元素的層級(jí),層級(jí)值(_depth)越大,層級(jí)越深,顯示的層也就越靠前。
  8. reassemble方法
    debug熱重載時(shí)會(huì)調(diào)用。該方法處理將元素自身標(biāo)記為需要build外(調(diào)用 markNeedsBuild 方法),還會(huì)遞歸遍歷全部子節(jié)點(diǎn),調(diào)用子節(jié)點(diǎn)的 reassemble 方法。
  9. markNeedsBuild方法
    _dirty = true;  // 標(biāo)記元素為dirty狀態(tài)
    owner!.scheduleBuildFor(this);  // 會(huì)調(diào)用rebuild方法。
  10. updateChild
    渲染過程的核心方法,通過新的組件配置來更新子元素。存在4種情況:
      1. 如果child為空而newWidget不為空,則創(chuàng)建一個(gè)新元素來渲染。
      2. 如果child不為空而newWidget為空,則表明組件配置中已經(jīng)沒有child這個(gè)元素了,因此需要移除它。
      3. 如果二者都不為空,則需要根據(jù)child的當(dāng)前是否可以更新(Widget.canUpdate)來處理,如果可以更新,那么使用新的組件配置更新元素;否則需要移除舊的元素,并使用新的組件配置創(chuàng)建一個(gè)新的元素。
      4. 如果二者都為空,那就什么都不做。
   返回的結(jié)果也分為3種情況:
      1. 如果創(chuàng)建了一個(gè)新的元素,則返回新構(gòu)建的子元素。
      2. 如果舊的元素被更新,返回更新后的子元素。
      3. 如果子元素被移除,而沒有新的替換的話,返回null。
  11. mount方法
    在新元素首次被創(chuàng)建時(shí)調(diào)用,按照給定的插入位置將元素插入給定的父節(jié)點(diǎn)。
    調(diào)用該方法后,元素的狀態(tài)會(huì)從 initial 改為 active。
    會(huì)將子元素的層級(jí)(_depth)設(shè)置為父元素的層級(jí)+1。
  12. unmount方法
    狀態(tài)從inactive改為defunct(不再存在)時(shí)調(diào)用,此時(shí)元素將脫離渲染樹,并且再也不會(huì)在渲染樹中存在。
  13. update方法
    當(dāng)父節(jié)點(diǎn)使用新的配置組件更新元素時(shí)調(diào)用(要求新的配置類型和舊的保持一致)。
  14. detachRenderObject和 attachRenderObject方法
    分別對(duì)應(yīng)從組件樹移除和添加RenderObject。
  15. deactivateChild方法
    將子元素加入到不活躍的元素列表,之后再從渲染樹中移除。
  16. activate方法
    狀態(tài)從inactive切換到active時(shí)調(diào)用。
    注意組件第一次掛載的時(shí)候不會(huì)調(diào)用這個(gè)方法而是mount方法。
  17. deactivate方法
    狀態(tài)從active切換到inactive時(shí)調(diào)用,也就是元素被移入到不活躍列表的時(shí)候會(huì)被調(diào)用。
  18. didChangeDependencies方法
    當(dāng)元素的依賴發(fā)生改變時(shí)調(diào)用,該方法也會(huì)調(diào)用 markNeedBuild 方法。
  19. rebuild方法
    當(dāng)元素的BuildOwner對(duì)象調(diào)用scheduleBuildFor方法時(shí)會(huì)調(diào)用本方法。
    調(diào)用performRebuild方法來重建元素。
    // 首次裝載時(shí)是在mount方法中觸發(fā),配置組件更改時(shí)會(huì)在build方法觸發(fā)。
  20. performRebuild方法
    在Element中,該方法是個(gè)空方法,需要子類去實(shí)現(xiàn)(即每個(gè)元素具體怎么重建由子類來決定)。
/*
先來看StatefulElement的performRebuild實(shí)現(xiàn)。
@override
void performRebuild() {
  if (_didChangeDependencies) {
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}
繼續(xù)看StatefulElement的父類ComponentElement的performRebuild實(shí)現(xiàn)。
@override
void performRebuild() {
  // 省略調(diào)試的代碼
  Widget? built;
  try {
    // ...
    built = build();  // 所以build方法中不能調(diào)用setState方法,會(huì)導(dǎo)致循環(huán)調(diào)用。
    // ...
  } catch (e, stack) {
    // ...
  } finally {
    _dirty = false;
    // ...
  }
  try {
    _child = updateChild(_child, built, slot);
    assert(_child != null);
  } catch (e, stack) {...//}
}
通過 built = build() 獲取了最新的Widget,再調(diào)用updateChild方法更新子元素。
*/

State狀態(tài)管理(響應(yīng)式編程的永恒主題)

無論是在React/Vue(支持響應(yīng)式編程的Web開發(fā)框架)還是Flutter中,討論的問題和解決的思想都是一致的。

管理狀態(tài)的方法:
  1. Widget管理自身狀態(tài)。
  2. Widget管理子Widget狀態(tài)。
  3. 混合管理(父子Widget都管理狀態(tài))。
  4. 全局狀態(tài)管理(跨組件狀態(tài)共享)。
    例:如果有一個(gè)設(shè)置頁(可設(shè)置應(yīng)用的語言),改變語言后,希望APP中依賴應(yīng)用語言的組件都能重新build一下,但這些依賴應(yīng)用語言的組件和設(shè)置頁并沒有關(guān)聯(lián)。
    需要通過一個(gè)全局狀態(tài)管理器來處理這種相距較遠(yuǎn)的組件之間的通信。目前主要有兩種辦法:
      1. 實(shí)現(xiàn)一個(gè)全局的事件總線,將語言狀態(tài)改變對(duì)應(yīng)為一個(gè)事件,然后在APP中依賴應(yīng)用語言的組件的initState 方法中訂閱語言改變的事件。當(dāng)用戶在設(shè)置頁切換語言后,發(fā)布語言改變事件,而訂閱了此事件的組件就會(huì)收到通知,收到通知后調(diào)用setState(...)方法重新build一下自身即可。
      2. 使用InheritedWidget組件或Provider、Redux等狀態(tài)管理庫(詳情見功能組件篇)。

如何決定使用哪種管理方法:
  1. 如果狀態(tài)是用戶數(shù)據(jù),如復(fù)選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父Widget管理。
  2. 如果狀態(tài)是有關(guān)界面外觀效果的,例如顏色、動(dòng)畫,那么狀態(tài)最好由Widget本身來管理。
  3. 如果某一個(gè)狀態(tài)是不同Widget共享的則最好由它們共同的父Widget管理。
  4. 在Widget內(nèi)部管理狀態(tài)封裝性會(huì)好一些,而在父Widget中管理會(huì)比較靈活。
  5. 組件之間相距較遠(yuǎn)或沒有關(guān)聯(lián)時(shí)使用全局狀態(tài)管理。

示例(管理自身狀態(tài))

//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);
  @override
  _TapboxAState createState() => new _TapboxAState();
}
/*
_TapboxAState 類:
    管理TapboxA的狀態(tài)。
    定義_active:確定盒子的當(dāng)前顏色的布爾值。
    定義_handleTap()函數(shù),該函數(shù)在點(diǎn)擊該盒子時(shí)更新_active,并調(diào)用setState()更新UI。
    實(shí)現(xiàn)widget的所有交互式行為。
*/
class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }
  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            _active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

示例(管理子組件TapboxB的狀態(tài))

//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => new _ParentWidgetState();
}
/*
ParentWidgetState 類:
    為TapboxB 管理_active狀態(tài)。
    實(shí)現(xiàn)_handleTapboxChanged(),當(dāng)盒子被點(diǎn)擊時(shí)調(diào)用的方法。
    當(dāng)狀態(tài)改變時(shí),調(diào)用setState()更新UI。
TapboxB 類:
    繼承StatelessWidget類,因?yàn)樗袪顟B(tài)都由其父組件處理。
    當(dāng)檢測(cè)到點(diǎn)擊時(shí),它會(huì)通知父組件。
*/
class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);
  final bool active;
  final ValueChanged<bool> onChanged;
  void _handleTap() {
    onChanged(!active);
  }
  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

示例(混合管理)

/*
_ParentWidgetStateC對(duì)象:
    管理_active 狀態(tài)。
    實(shí)現(xiàn) _handleTapboxChanged方法(當(dāng)盒子被點(diǎn)擊時(shí)調(diào)用),改變_active狀態(tài)并調(diào)用setState()更新UI。
_TapboxCState對(duì)象:
    管理_highlight 狀態(tài)。
    GestureDetector監(jiān)聽tap事件。
*/
//---------------------------- ParentWidget ----------------------------
class ParentWidgetC extends StatefulWidget {
  @override
  _ParentWidgetCState createState() => new _ParentWidgetCState();
}
class _ParentWidgetCState extends State<ParentWidgetC> {
  bool _active = false;
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
  TapboxC({Key key, this.active: false, @required this.onChanged})
      : super(key: key);
  final bool active;
  final ValueChanged<bool> onChanged;
  @override
  _TapboxCState createState() => new _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;
  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }
  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }
  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }
  void _handleTap() {
    widget.onChanged(!widget.active);
  }
  @override
  Widget build(BuildContext context) {
    // 在按下時(shí)添加綠色邊框,當(dāng)抬起時(shí),取消高亮  
    return new GestureDetector(
      onTapDown: _handleTapDown, // 處理按下事件
      onTapUp: _handleTapUp, // 處理抬起事件
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: new Container(
        child: new Center(
          child: new Text(widget.active ? 'Active' : 'Inactive',
              style: new TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? new Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}

示例(優(yōu)化:顯示和邏輯分離)

========================原代碼=============
class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
  int _counter = 0;
  void _increment() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: _increment,
          child: new Text('Increment'),
        ),
        new Text('Count: $_counter'),
      ],
    );
  }
}

========================優(yōu)化后=============
將計(jì)數(shù)器顯示和更改邏輯分離。
責(zé)任分離允許將復(fù)雜性邏輯封裝在各個(gè)widget中,同時(shí)保持父項(xiàng)的簡(jiǎn)單性。

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});
  final int count;
  @override
  Widget build(BuildContext context) {
    return new Text('Count: $count');
  }
}
class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});
  final VoidCallback onPressed;
  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      onPressed: onPressed,
      child: new Text('Increment'),
    );
  }
}
class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
  int _counter = 0;
  void _increment() {
    setState(() {
      ++_counter;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Row(children: <Widget>[
      new CounterIncrementor(onPressed: _increment),
      new CounterDisplay(count: _counter),
    ]);
  }
}

示例

void main() {
  runApp(new MaterialApp(
    title: 'Shopping App',
    home: new ShoppingList(
      products: <Product>[
        new Product(name: 'Eggs'),
        new Product(name: 'Flour'),
        new Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}
class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);
  final List<Product> products;
  @override
  _ShoppingListState createState() => new _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = new Set<Product>();
  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      if (inCart)
        _shoppingCart.add(product);
      else
        _shoppingCart.remove(product);
    });
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Shopping List'),
      ),
      body: new ListView(
        padding: new EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return new ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}
// 商品
class Product {
  const Product({this.name});
  final String name;
}
typedef void CartChangedCallback(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({Product product, this.inCart, this.onCartChanged})
      : product = product,
        super(key: new ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;
  Color _getColor(BuildContext context) {
    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }
  TextStyle _getTextStyle(BuildContext context) {
    if (!inCart) return null;
    return new TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }
  @override
  Widget build(BuildContext context) {
    return new ListTile(
      onTap: () {
        onCartChanged(product, !inCart);
      },
      leading: new CircleAvatar(
        backgroundColor: _getColor(context),
        child: new Text(product.name[0]),
      ),
      title: new Text(product.name, style: _getTextStyle(context)),
    );
  }
}

3. 內(nèi)置組件庫、自定義組件

  1. 內(nèi)置組件庫

Flutter提供了一套豐富強(qiáng)大的基礎(chǔ)組件庫,在基礎(chǔ)組件庫之上又提供了一套Material風(fēng)格(Android默認(rèn)的視覺風(fēng)格)和一套Cupertino風(fēng)格(iOS視覺風(fēng)格)的組件庫。

  1. 基礎(chǔ)組件庫
需要導(dǎo)入:import 'package:flutter/widgets.dart';
如果引入了Material或Cupertino則不需要再引入,因?yàn)樗麄儍?nèi)部已經(jīng)引入過了。
Text
  文本組件。
    
Row、 Column
  對(duì)子組件水平、垂直布局。
  其設(shè)計(jì)是基于Web開發(fā)中的Flexbox布局模型。
    
Stack
  允許子widget堆疊 (類似Android的FrameLayout), 可使用 Positioned來定位子組件相對(duì)于Stack的上下左右的位置。
  其設(shè)計(jì)是基于Web開發(fā)中的絕對(duì)定位布局模型。
    
Container
  矩形容器。
  可以裝飾一個(gè)BoxDecoration(background、邊框、陰影)。有邊距、填充和約束。
  類似html的div。
  1. Material組件庫
需要導(dǎo)入:import 'package:flutter/material.dart';

Flutter提供了一套豐富的Material組件,它可以構(gòu)建遵循Material Design設(shè)計(jì)規(guī)范的應(yīng)用程序。
Material應(yīng)用程序以MaterialApp組件開始, 該組件在應(yīng)用程序的根部創(chuàng)建了一些必要的組件,比如Theme組件,它用于配置應(yīng)用的主題。 是否使用MaterialApp完全是可選的,但建議使用。
Material 組件庫中有一些組件可以根據(jù)實(shí)際運(yùn)行平臺(tái)來切換表現(xiàn)風(fēng)格,比如MaterialPageRoute,在路由切換時(shí),如果是Android系統(tǒng),它將會(huì)使用Android系統(tǒng)默認(rèn)的頁面切換動(dòng)畫(從底向上);如果是iOS系統(tǒng),它會(huì)使用iOS系統(tǒng)默認(rèn)的頁面切換動(dòng)畫(從右向左)。
  1. Cupertino組件庫
需要導(dǎo)入:import 'package:flutter/cupertino.dart';

Flutter提供了一套豐富的Cupertino風(fēng)格的組件(目前沒有Material組件豐富,仍在不斷的完善中)。

示例(使用Cupertino組件)

import 'package:flutter/cupertino.dart';
class CupertinoTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text("Cupertino Demo"),
      ),
      child: Center(
        child: CupertinoButton(
            color: CupertinoColors.activeBlue,
            child: Text("Press"),
            onPressed: () {}
        ),
      ),
    );
  }
}

示例(Material組件)

// (默認(rèn)已經(jīng)設(shè)置)確保在pubspec.yaml文件中,將flutter的值設(shè)置為:uses-material-design: true。
import 'package:flutter/material.dart';
void main() {
  runApp(new MaterialApp(    // 為了繼承主題數(shù)據(jù),widget需要位于MaterialApp內(nèi)才能正常顯示
    title: 'My app', 
    home: new MyScaffold(),
  ));
}
// MyScaffold 通過一個(gè)Column widget,在垂直方向排列其子項(xiàng)。在Column的頂部,放置了一個(gè)MyAppBar實(shí)例,將一個(gè)Text widget作為其標(biāo)題傳遞給應(yīng)用程序欄。最后,MyScaffold使用了一個(gè)Expanded來填充剩余的空間,正中間包含一條message。
class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Material 是UI呈現(xiàn)的“一張紙”
    return new Material(
      // Column is 垂直方向的線性布局.
      child: new Column(
        children: <Widget>[
          new MyAppBar(
            title: new Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
          new Expanded(
            child: new Center(
              child: new Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}
// 在MyAppBar中創(chuàng)建一個(gè)Container,高度為56像素(像素單位獨(dú)立于設(shè)備,為邏輯像素),其左側(cè)和右側(cè)均有8像素的填充。在容器內(nèi)部, MyAppBar使用Row 布局來排列其子項(xiàng)。 中間的title widget被標(biāo)記為Expanded, ,這意味著它會(huì)填充尚未被其他子項(xiàng)占用的的剩余可用空間。Expanded可以擁有多個(gè)children, 然后使用flex參數(shù)來確定他們占用剩余空間的比例。
class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});
  final Widget title;
  @override
  Widget build(BuildContext context) {
    return new Container(
      height: 56.0, // 單位是邏輯上的像素(并非真實(shí)的像素,類似于瀏覽器中的像素)
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: new BoxDecoration(color: Colors.blue[500]),
      // Row 是水平方向的線性布局(linear layout)
      child: new Row(
        // 列表項(xiàng)的類型是 <Widget>
        children: <Widget>[
          new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null 會(huì)禁用 button
          ),
          // Expanded expands its child to fill the available space.
          new Expanded(
            child: title,
          ),
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}
  1. 自定義組件

當(dāng)Flutter提供的現(xiàn)有組件無法滿足需求,或者為了共享代碼需要封裝一些通用組件時(shí),就需要自定義組件。

3種方式:
  1. 組合其它組件(通過組合其它組件來組成一個(gè)新組件)
  2. 自繪(通過CustomPaint和Canvas來實(shí)現(xiàn))
  3. 實(shí)現(xiàn)RenderObject

詳見篇11(自定義組件)

其他

  1. 解決Flutter嵌套過深問題
1. 使用類構(gòu)建(建議)的好處
  1. 支持性能優(yōu)化(如:使用const構(gòu)造方法)
  2. 兩個(gè)不同的布局切換時(shí),能夠正確地銷毀對(duì)應(yīng)的資源。
  3. 保證正確的方式進(jìn)行熱重載,而使用函數(shù)可能破壞熱重載。
  4. 在 Widget Inspector 中可以查看得到,從而可以方便定位和調(diào)試問題。
  5. 更友好的錯(cuò)誤提示。當(dāng)組件樹出現(xiàn)錯(cuò)誤時(shí),框架會(huì)給出當(dāng)前構(gòu)建得組件名稱,而如果使用函數(shù)的話則得不到清晰的名詞。
  6. 可以使用 key 提高性能。
  7. 可以使用 context 提供的方法(函數(shù)式組件需要顯示地傳遞context)。

2. 使用函數(shù)構(gòu)建(不建議)的好處
  1. 代碼量少(通過快捷代碼片段構(gòu)建類時(shí),相差不多)。
    functional_widget插件(通過注解將和函數(shù)式組件構(gòu)建方式自動(dòng)轉(zhuǎn)換為類組件的代碼生成插件)
最后編輯于
?著作權(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ù)。

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

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