書名: Flutter 開發之旅從南到北作者名: 楊加康本章字數: 3158字更新時間: 2021-01-15 12:17:25
1.4 第一個應用——計數器
在真正領略Flutter的風采之前,我們可以參考本書的附錄A安裝Flutter開發環境。完成安裝操作之后,讀者現在就可以跟隨本節的內容嘗試在沒有Dart語言基礎的情況下理解Flutter默認實現的一個計數器應用,你會發現一切都比你想象中的簡單很多。
1.4.1 創建第一個應用
為了使各個階段的開發者都能夠輕松地完成本書中的操作,本書將會統一采用更加輕量的Visual Studio Code作為編寫代碼和展示示例的IDE,并且使用macOS進行演示(Windows系統中的操作與之類似)。可以按照如下步驟創建第一個Flutter應用。
(1)啟動Visual Studio Code。
(2)選擇菜單欄中的“查看”→“命令面板”,如圖1.6所示,打開命令面板。

圖1.6 選擇“查看”→“命令面板”
(3)在命令面板中輸入“flutter”,并選擇下拉列表框中的“Flutter: New Project”,如圖1.7所示,新建一個Flutter項目。

圖1.7 選擇下拉列表框中的“Flutter: New Project”
(4)輸入自定義的項目名稱(如hello_world),如圖1.8所示,按Return鍵創建項目。

圖1.8 輸入項目名稱
(5)單擊Select a folder to create the project in按鈕(見圖1.9),指定項目將要放置的位置,這里選擇目錄后,單擊OK按鈕。

圖1.9 單擊Select a folder to create the project in按鈕
(6)等待項目創建結束之后,新項目窗口便會自動打開。
1.4.2 Flutter項目的結構
創建Flutter項目后,可以在Visual Studio Code中看到Flutter項目的結構,如圖1.10所示。

圖1.10 Flutter項目的結構
部分文件夾的作用如下。
- android文件夾:存放Flutter與Android原生交互的代碼,該文件夾下的文件和單獨創建的Android項目基本一樣。
- ios文件夾:對于標準的iOS項目,存放Flutter與iOS原生交互的代碼。
- lib文件夾:Flutter的核心目錄,存放的是使用Dart語言編寫的代碼。不管是Android平臺,還是iOS平臺,安裝、配置開發環境后,都可以在對應的設備或模擬器上面運行這里的Dart代碼,而整個應用的入口是lib文件夾下的main.dart文件。也可以在這個lib文件夾下面創建不同的文件夾,里面存放了不同的文件來管理日益壯大的應用。
- test文件夾:存放Flutter的測試代碼。
注意,pubspec.yaml文件是項目的配置文件,可以在該文件中聲明項目中使用到的依賴庫、環境版本以及資源文件等。附錄A會介紹更多相關內容。
pubspec.yaml的另一個重要功能便是指定應用中需要使用的本地資源(圖片、字體、音頻、視頻等)。通常情況下,我們會在項目根目錄下創建一個images目錄,用來存放應用中會使用到的圖片資源,這些圖片資源需要在該配置文件中的assent屬性下聲明(見圖1.11)。

圖1.11 pubspec.yaml中的資源文件聲明
應用運行在設備上之后,這些資源文件就會一并打包在安裝程序中,在之后的章節會對配置文件的其他配置項做具體的介紹。
熟悉了這些文件夾的大致作用之后,我們先嘗試運行一下這個默認的項目。為此,我們需要啟動模擬器或者使用USB接口接入真機。具體步驟如下。
(1)模擬器打開或者真機接入后,在Visual Studio Code主界面右下角的狀態欄中選擇可以運行的目標設備(見圖1.12)。

圖1.12 選擇可用設備
(2)打開lib文件夾下的main.dart文件,按F5鍵或選擇菜單欄中的“調試”→“啟動調試”,開始運行項目(見圖1.13)。

圖1.13 選擇“調試”→“啟動調試”
(3)等待應用在模擬器或真機上自動啟動。
(4)如果一切正常,在應用安裝成功后,我們應該就能夠在設備上看到圖1.14所示的計數器應用。

圖1.14 計數器應用
此時,我們的第一個應用就已經啟動了,你可以看到這個應用的首頁展示了一個標準的Material Design風格的界面,頂部有一個帶有頁面標題的導航欄,右下角有一個帶有“+”號的懸浮按鈕,單擊這個按鈕就會使頁面中的數字增加。這個應用可以用來記錄單擊按鈕的次數。
1.4.3 計數器應用的實現
已經運行的計數器應用是我們步入Flutter殿堂的階梯。通過分析這個應用的實現方式,我們會對Flutter中的應用開發有一個直觀的理解。
首先,打開lib文件夾下的main.dart文件,這里面存放了這個計數器應用的所有代碼。忽視注釋中的內容,我們可以在文件的最上方看到帶有import字樣的代碼行,它的作用是導入該文件需要使用到的庫。這里我們導入了Material庫,因為我們需要使用該這個庫下面的UI組件。下面我們可以看到一個main()函數,它是Dart語言的主函數,每當我們運行應用后,系統都會首先調用main.dart文件中的這個函數。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
main()函數中調用了runApp()函數,我們可以將runApp()理解為運行Flutter應用的入口,而傳入的MyApp對象就代表了需要運行的應用。在Flutter中,MyApp又稱為組件對象,它在這里就相當于應用顯示在屏幕上的UI組件,應用啟動后就能夠顯示MyApp中的內容。下面是MyApp組件的具體實現。
class MyApp extends StatelessWidget {
// 重寫StatelessWidget的build()方法,返回一個組件對象
@override
Widget build(BuildContext context) {
/*
* MaterialApp表明應用采用Material Design風格,
* 可以在theme屬性下配置應用中與主題相關的屬性,如顏色、按鈕風格
* */
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
通過閱讀上面的代碼,我們發現類MyApp繼承自StatelessWidget,并重寫了它的build()方法,這個方法返回了一個組件對象,所以這里我們可以推理出MaterialApp()也是一個組件對象。前面已經提到了“一切皆為組件”,讀者可以從這里開始隨著閱讀本書慢慢地理解這句話了,它是我們開發出用戶能看見的應用的基礎,我們可以通過設置組件的屬性來控制應用所能展示的內容。
繼續分析下面的代碼,我們可以看到在MaterialApp組件中有3個屬性,分別是title、theme、home。其中,title表示組件的標題屬性;theme可以用來配置應用的主題樣式;home參數用來指定MaterialApp中的主體內容,它接受另一個組件,這里指定為MyHomePage。在使用MyHomePage時,還傳入了一個title參數,它用來接受顯示在計數器應用頂部導航欄中的標題。我們可以嘗試修改這個值然后保存代碼,如果程序依然處于運行狀態,由于Flutter支持熱加載的特性,導航欄中的文字就會實時更新為最新的值,這個特性能夠幫助我們更高效地開發應用。
繼續向下,我們就可以看到MyHomePage組件的具體實現了。
class MyHomePage extends StatefulWidget {
// 構造函數,用于接受調用者的參數
MyHomePage({Key key, this.title}) : super(key: key);
// 聲明了一個字符串類型的final變量,并在構造函數中初始化
final String title;
/*
* 所有繼承自StatefulWidget的組件都要重寫createState() 方法,
* 用于指定該頁面的狀態是由誰來控制的。
* 在Dart中,以下劃線開頭的變量和方法的默認訪問權限就是私有的,
* 類似于Java中用private關鍵字修飾的變量和方法,只能在類的內部訪問
*/
@override
_MyHomePageState createState() => _MyHomePageState();
}
/*
* State是一個狀態對象,<> 里面表示該狀態是與誰綁定的。
* 在修改狀態時,在該類中進行編寫
*/
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
// 實現計數值加1的函數
void _incrementCounter() {
// setState方法用于更新屬性
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
/*
* Scaffold是一個Material Design風格的組件,
* 它繼承自StatefulWidget,包含appBar、body、drawer等屬性
* */
return Scaffold(
/* 頂部導航欄 */
appBar: AppBar(
/*
* 這里的Widget其實就是MyHomePage,
* 它在這里調用了上面傳遞過來的title變量
*/
title: Text(widget.title),
),
// Scaffold中的主體布局
body: Center(
/*
* 在Center組件中有一個child屬性,用來定義它的子組件Column,
* Column表示以行的形式顯示其子組件
*/
child: Column(
/*
* mainAxisAlignment用來控制子組件的對齊方式,
* 也可以把值設置為start、end等
*/
mainAxisAlignment: MainAxisAlignment.center,
/*
* Column組件的children屬性用于指定它的子組件,
* 它接受一個數組,可以向該屬性傳遞多個組件
*/
children: <Widget>[
// Text組件,用于顯示文本
Text(
'You have pushed the button this many times:',
),
// Text組件,使用style屬性來設置它的樣式
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
/*
* FloatingActionButton也是Material Design風格的組件,
* 可以在onPressed屬性中定義其單擊事件
*/
floatingActionButton: FloatingActionButton(
// 通過單擊觸發_incrementCounter函數
onPressed: _incrementCounter,
tooltip: 'Increment',
// 指定child的子組件為一個“+”號圖標
child: Icon(Icons.add),
),
);
}
}
MyHomePage是實現計數器應用的核心,它同樣是一個組件,最終會顯示在應用中。在_MyHomePageState中,我們可以重寫build()方法,返回MyHomePage組件中顯示的內容。根據注釋,我們可以在代碼中找到顯示在屏幕中的組件,其中涉及了Scaffold、Column、Text等常用組件,以及對FloatingActionButton響應事件的處理。Dart語言的相關語法和組件的具體含義會在下面的章節中介紹。
在上述代碼中,很多組件都有child屬性,如FloatingActionButton組件中的child屬性是一個“+”圖標,它就表示將“+”圖標設置為FloatingActionButton的子組件,表現在屏幕上的效果就是“+”圖標顯示在FloatingActionButton中。通過這種方法,我們可以將多個組件組合在一起而開發出一個完整的頁面。
另外,讀者還可能注意到一個重要的部分,MyHomePage繼承自StatefulWidget,MyApp也繼承自一個與它類似的StatelessWidget。作為計數器應用的核心組件,MyHomePage用于改變計數值。也就是說,當單擊“+”按鈕后,增加計數這個功能需要由它負責。要想達到這樣的效果,必然就需要改變_counter變量的值,StatefulWidget是可以改變它對應State對象中的值的一個組件,而StatelessWidget不具備這個功能,它只能用來展示UI。第3章會具體介紹StatefulWidget和StatelessWidget這兩個類的具體用法。