一、程式入口
一般情況下,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 的生命週期
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 的生命週期
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 移除