首頁>技術>

一、程式入口

一般情況下,Flutter 的主入口是 main.dart。

1、介面的本質(Widget)

在 main 函式中透過 runApp 函式啟動一個 Flutter 介面,而 runApp(Widget app) 函式接收的引數 Widget app 就是介面了,即介面是一個 Widget:

main() {  runApp(Center(    child: Text("Hello world",        textDirection: TextDirection.ltr,        style: TextStyle(fontSize: 30, color: Colors.orange)),  ));}

在 Flutter 中,所有的介面、控制元件都是 Widget

2、MaterialApp

為了快速開發一個標準 material 的介面,Flutter 提供了 MaterialApp 這個 Widget:

import 'package:flutter/material.dart';main() {  // 1. runApp 函式  runApp(MaterialApp(    // debugShowCheckedModeBanner: false, // 控制介面右上角是否顯示`debug`提示    home: Scaffold(      appBar: AppBar(        title: Text("第一個Flutter程式"),      ),      body: Center(        child: Text(          "Hello World",          textDirection: TextDirection.ltr,          style: TextStyle(fontSize: 30, color: Colors.orange),        ),      ),    ),  ));}

Scaffold 也是 Widget,翻譯為腳手架,可以幫助開發者搭建出帶 AppBar 的介面

二、State Widget

萬物基於 Widget 的 Flutter 把 Widget 分為兩類,分別是:

StatelessWidget: 無狀態的 Widget,內容是確定沒有狀態(data)的改變StatefulWidget: 有狀態的 Widget,在執行過程中有一些狀態(data)需要改變

1、StatelessWidget

上面的 demo 中,runApp(Widget app)、MaterialApp({this.home})、Scaffold({this.body}) 這 3 個方法接收的主要引數都是 Widget 型別,並且不涉及狀態的儲存,因此可以把它們抽成對應的 3 個 StatelessWidget 來使用,這樣就可以有效的避免 巢狀地獄 的發生:

這種抽取出一個個 Widget 的方式,類似於大前端中的元件化開發

import 'package:flutter/material.dart';main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      debugShowCheckedModeBanner: false,      home: HomePage(),    );  }}class HomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("第一個Flutter程式"),      ),      body: ContentBody(),    );  }}class ContentBody extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Center(      child: Text(        "Hello World",        textDirection: TextDirection.ltr,        style: TextStyle(fontSize: 30, color: Colors.orange),      ),    );  }}

AndroidStudio 中的 State Widget 快速建立模板:

stless:輸入 stless 直接生成 StatelessWidget

stful:輸入 stful 直接生成 StatefulWidget

2、StatefulWidget

Flutter 是宣告式開發,即一旦狀態發生變化,介面會自動改變,這裡說的狀態就是資料。StatefulWidget 就是可以有狀態的 Widget,一個簡單的例子:寫一個同意協議控制元件:

/// flag:狀態/// Stateful不能定義狀態 -> 建立一個單獨的State類,這個State類負責維護狀態class ContentBody extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return ContentBodyState();  }}/// 使用State類來儲存狀態class ContentBodyState extends State<ContentBody> {  var flag = true;  @override  Widget build(BuildContext context) {    return Center(      child: Row(        mainAxisAlignment: MainAxisAlignment.center,        children: <Widget>[          Checkbox(            value: flag,            onChanged: (value) {              /// flag = value; // 無效程式碼,不會重新整理介面              /// 必須使用 setState() 方法強制介面重新整理              setState(() {                flag = value;              });            },          ),          Text("同意協議", style: TextStyle(fontSize: 20)),        ],      ),    );  }}

注意:

Flutter 中的狀態(State)和 React 中的狀態概念一致。

單單修改資料是不行的,還必須呼叫 setState() 通知介面在下一幀重新繪製才行。

3、為什麼 State Widget 本身不能定義狀態?

無論 StatelessWidget 還是 StatefulWidget,其父類都是 Widget,來看看 Widget 的定義:

@immutableabstract class Widget extends DiagnosticableTree {    ...}

Widget 有 @immutable 註解,就意味著 Widget 的所有子類即使有成員屬性,也一定是使用 final 修飾的,試問 final 變數會有變化嗎?

/// 所有的Widget類中都不能定義狀態,類成員屬性必須是finalclass ContentBody extends StatelessWidget {  // IDE報錯:This class (or a class that this class inherits from) is marked as '@immutable', but one of more of its instance fields aren't final: ContentBody.flag  // 錯誤程式碼  // @immutable 註釋過的類,成員屬性必須是final的  var flag = true;  @override  Widget build(BuildContext context) {    return Center(      child: Row(        mainAxisAlignment: MainAxisAlignment.center,        children: [          Checkbox(value: flag, onChanged: (value) => flag = value),          Text("同意協議", style: TextStyle(fontSize: 20)),        ],      ),    );  }}

三、綜合案例

1、StatelessWidget 綜合案例(商品列表)
import 'package:flutter/material.dart';main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: HomePage(),    );  }}class HomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("商品列表"),      ),      body: HomeContent(),    );  }}class HomeContent extends StatelessWidget {  @override  Widget build(BuildContext context) {    // Column 會越界relayoutBoundary,改成ListView就好了    return ListView(      children: [        HomeProductItem("Apple1", "Macbook1",            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),        SizedBox(height: 6),        HomeProductItem("Apple2", "Macbook2",            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),        SizedBox(height: 6),        HomeProductItem("Apple3", "Macbook3",            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),      ],    );  }}class HomeProductItem extends StatelessWidget {  final String title;  final String desc;  final String imageURL;  final style1 = TextStyle(fontSize: 25, color: Colors.orange);  final style2 = TextStyle(fontSize: 20, color: Colors.green);  HomeProductItem(this.title, this.desc, this.imageURL);  @override  Widget build(BuildContext context) {    return Container(      padding: EdgeInsets.all(8),      decoration: BoxDecoration(        border: Border.all(          width: 5, // 設定邊框的寬度          color: Colors.pink, // 設定邊框的顏色        ),      ),      child: Column(        // crossAxisAlignment: CrossAxisAlignment.start,        // crossAxisAlignment: CrossAxisAlignment.center,        // crossAxisAlignment: CrossAxisAlignment.end,        // crossAxisAlignment: CrossAxisAlignment.stretch,        children: [          // Text(title, style: style1, textAlign: TextAlign.center),          Text(title, style: style1),          SizedBox(height: 8),          Text(desc, style: style2),          SizedBox(height: 8),          Image.network(imageURL),        ],      ),    );  }}

2、StatefulWidget 綜合案例(計數器)
import 'package:flutter/material.dart';main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: HomePage(),    );  }}class HomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text("title")),      body: HomeContent("hello world"),    );  }}/// 為什麼Flutter在設計的時候StatefulWidget的build方法放在State中/// 1. build出來的Widget是需要依賴State中的變數(狀態/資料)/// 2. 在Flutter的執行過程中:///   Widget是不斷的銷燬和建立的///   當我們自己的狀態發生改變時,並不希望重新建立一個新的Stateclass HomeContent extends StatefulWidget {  final String message;  HomeContent(this.message);  @override  _HomeContentState createState() => _HomeContentState();}class _HomeContentState extends State<HomeContent> {  int _counter = 0;  @override  Widget build(BuildContext context) {    return Center(      child: Column(        mainAxisAlignment: MainAxisAlignment.center,        children: [          _getButtons(),          Text("當前計數:$_counter", style: TextStyle(fontSize: 25)),          Text("傳遞的資訊:${widget.message}")        ],      ),    );  }  Widget _getButtons() {    return Row(      mainAxisAlignment: MainAxisAlignment.center,      children: [        ElevatedButton(          child: Text("+", style: TextStyle(fontSize: 20)),          style: TextButton.styleFrom(backgroundColor: Colors.pink),          onPressed: () {            setState(() {              _counter++;            });          },        ),        ElevatedButton(          child: Text("-", style: TextStyle(fontSize: 20)),          style: TextButton.styleFrom(backgroundColor: Colors.purple),          onPressed: () {            setState(() {              _counter--;            });          },        ),      ],    );  }}

四、生命週期

1、StatelessWidget 的生命週期

因為 StatelessWidget 沒有狀態,所以它的生命週期很簡單:先呼叫 建構函式,再呼叫 build(),沒了:

import 'package:flutter/material.dart';main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: HomePage(),    );  }}class HomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text("title")),      body: HomeContent("hello world"),    );  }}// StatelessWidget 的生命週期class HomeContent extends StatelessWidget {  final String message;  HomeContent(this.message) {    print("建構函式被呼叫");  }  @override  Widget build(BuildContext context) {    print("呼叫build方法");    return Text(message);  }}

日誌輸出:

I/flutter ( 3092): 建構函式被呼叫I/flutter ( 3092): 呼叫build方法

2、StatefulWidget 的生命週期

方法

說明

createState

Framework 會透過呼叫 StatefulWidget.createState 來建立一個 State。

initState

新建立的 State 會和一個 BuildContext 產生關聯,此時認為 State 已經被安裝好了,initState 函式將會被呼叫。通常,我們可以重寫這個函式,進行初始化操作。

didChangeDependencies

在 initState 呼叫結束後,這個函式會被呼叫。事實上,當 State 物件的依賴關係發生變化時,這個函式總會被 Framework 呼叫。

build

經過以上步驟,系統認為一個 State 已經準備好了,就會呼叫 build 來構建檢視。我們需要在這個函式中返回一個 Widget。

deactivate

deactivate 當 State 被暫時從檢視樹中移除時,會呼叫這個函式。頁面切換時,也會呼叫它,因為此時 State 在檢視樹中的位置發生了變化,需要先暫時移除後新增。

dispose

當 State 被永久地從檢視樹中移除時,Framework 會呼叫該函式。在銷燬前觸發,我們可以在這裡進行最終的資源釋放。在呼叫這個函式之前,總會先呼叫 deactivate 函式。

didUpdateWidget

當 Widget 的配置發生變化時,會呼叫這個函式。比如,熱過載的時候就會呼叫這個函式。呼叫這個函式後,會呼叫 build 函式。

setState

當需要更新 State 的檢視時,需要手動呼叫這個函式,它會觸發 build 函式。

方法說明摘抄至:http://www.cxybcw.com/45008.html

官方文件(全):https://api.flutter.dev/flutter/widgets/State-class.html

// StatefulWidget 的生命週期class HomeContent extends StatefulWidget {  HomeContent(String message) {    print("1. 呼叫 HomeContent 的 constructor 方法");  }  @override  _HomeContentState createState() {    print("2. 呼叫 HomeContent 的 createState 方法");    return _HomeContentState();  }}class _HomeContentState extends State<HomeContent> {  _HomeContentState() {    print("3. 呼叫 _HomeContentState 的 constructor 方法");  }  @override  void initState() {    // 注意:這裡必須呼叫super(@mustCallSuper)    super.initState();    print("4. 呼叫 _HomeContentState 的 initState 方法");  }  @override  void didChangeDependencies() {    super.didChangeDependencies();    print("呼叫 _HomeContentState 的 didChangeDependencies 方法");  }  @override  void didUpdateWidget(covariant HomeContent oldWidget) {    super.didUpdateWidget(oldWidget);    print("呼叫 _HomeContentState 的 didUpdateWidget 方法");  }  @override  Widget build(BuildContext context) {    print("5. 呼叫 _HomeContentState 的 build 方法");    return Container();  }  @override  void dispose() {    super.dispose();    print("6. 呼叫 _HomeContentState 的 dispose 方法");  }}

日誌結果:

// 介面初始化I/flutter ( 3092): 1. 呼叫 HomeContent 的 constructor 方法I/flutter ( 3092): 2. 呼叫 HomeContent 的 createState 方法I/flutter ( 3092): 3. 呼叫 _HomeContentState 的 constructor 方法I/flutter ( 3092): 4. 呼叫 _HomeContentState 的 initState 方法I/flutter ( 3092): 呼叫 _HomeContentState 的 didChangeDependencies 方法I/flutter ( 3092): 5. 呼叫 _HomeContentState 的 build 方法// 點選計數按鈕I/flutter ( 3092): 5. 呼叫 _HomeContentState 的 build 方法

沒有列印 dispose()中的日誌,是因為這裡 Demo 中沒有將 Widget 移除

13
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • WIN32,VC程式開發,捕獲軟體所有異常的C++程式碼原始碼