在〈準備 Flutter 開發環境〉的最後,建立一個基本的 Flutter Application,其中的程式碼看來很長,不過多半是註解,為的是讓初接觸 Flutter 的開發者,可以一窺 Flutter 應用程式的概貌。
若有耐心且是個有經的開發者,讀一下註解,大致上是可以明白程式撰寫的起手式,不過,想掌握整個框架的話,還得自行研究元件間的關係、繼承架構、生命週期等,而範例程式碼是個基本模版,想從其中諸多類別認識這些觀念,就不是那麼容易了。
對我來說,寫出一個越簡單越好的 Hello, World! 程式,才代表對一個框架或平台有了初步認識,而底下是我的 Hello, World:
import 'package:flutter/material.dart';
void main() => runApp(
Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)
);
執行後可以在手機的頂端看到 Hello, World! 文字:
Flutter 應用程式使用 Dart 語言來撰寫,許多文件都會說,Dart 這門語言很簡單…
這要看你從哪個角度來看,就我而言,它是簡單沒錯,因此就像是 Java/Python/JavaScript 等語言的綜合體,如果你熟悉其中任何一門語言,在 Dart 中都可以看到類似的語法元素,對你來說可能就是簡單的,少有文件或書專門探討 Dart,多半是快速概覽一下,而且各有不同的概覽重點。
然而,另一個角度來看,如果對這幾個語言之一都不熟悉,或是沒能掌握這些語言中的一些高階概念,對你來說,Dart 語言就不是簡單的。
也就是說,目前 Flutter 的行銷對象,是對 Java/Python/JavaScript 之類語言有涉獵、對前端或手機裝置開發經驗的的開發者。
基本上,我會在必要時,稍微談一下 Dart,不過不是全部,只是記一些我想記錄的 Dart 特性。
在方才的範例中:
import 'package:flutter/material.dart';
.dart 是 Dart 原始碼檔案的副檔名,每個 .dart 可以視為組織程式碼的一個單元,就像是 Python 的 .py、JavaScript 的 .js 可視為一個模組。
import
代表匯入某個 .dart 中的函式、類別等 API,對於同屬於一個專案的 .dart,例如 lib/xxx.dart,可以僅撰寫 import 'lib/xxx.dart'
,若是 Dart 標準 API,import
時會使用 dart:
前置區別,例如 import 'dart:math'
,對於第三方發佈的套件,會使用 package:
前置區別,就像 import 'package:flutter/material.dart'
,因為是開發 Flutter 應用程式,匯入 package:flutter
,而 material.dart 是個門戶,匯出了許多 API。
Dart 的第三方套件,使用 pub package manager 來管理,類似 Python 的 pip
,Node.js 的 npm
,相依資訊是寫在 pubspec.yaml,你可以在專案中找到它,其中設定之意義,可參考〈How to use packages〉
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
接下來是 void main() => runApp(...)
,main
是 Dart 的程式進入點,也可以寫為區塊形式:
void main() {
runApp(
Text(
'Hello, World!',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)
);
}
在 Dart 中函式本體只有一行運算式或陳述句,就可以寫為 void main() => runApp(...)
的形式。
runApp
則是 Flutter 框架運行的起點,它接受一個 Widget
實例:
void runApp(Widget app)
Flutter 的設計受到 React 的啟發,如果你有 React 的經驗,在摸索一陣後,就可以看到一些 React 的影子。
Widget
就是其中一例,乍看它是組成畫面的元素,然而不是,其實 Widget
是個描述組態與狀態的物件,類似 React 的 React.Component
;在 Flutter 中,組成畫面的元素會使用 Element
實例表示,Element
某些程度上,可以比擬為 HTML DOM,具體而言,Element
是透過 Widget
的 createElemnet
建立:
abstract class Widget extends DiagnosticableTree {
...
@protected
Element createElement();
..
}
被指定給 runApp
的 Widget
,會作為 Widget
樹的根,透過 Widget
的 createElement
建立 Element
時,Element
會持有對 Widget
參考,Element
也會組成一棵樹,結構與 Widget
樹相同,Element
樹用來管理底層的渲染樹(render tree)。
最單純的想法可以認知為 Widget
樹有變動,Element
樹就會變化,渲染樹就會變化,畫面就會變動,當然實際這麼做的話,畫面重建的負擔很重,Flutter 還是會做適當的重用或最佳化。
例如,一個 Element
參考的 Widget
是可以更新的,也就是說,只要 Widget
樹結構不變,只是替換某個 Widget
,Element
樹不一定會改變,可能只是對應的 Element
更新了參考的 Widget
,也就是說,Widget
樹變化了,Flutter 還是會判斷是否重建 Element
樹或渲染樹的。
在沒有做任何版面配置的情況下,指定給 runApp
的 Widget
,會佔滿整個螢幕,只不過就這個範例來說,繪製出來的文字只佔了一個小角落,也就是左上角 Hello, World! 字樣的部份。
在 Text
的建構部份,並沒有看到 new
,因為 Dart 2 以後,建構實例不必使用 new
(選擇性語法,你想寫也可以);Text
第一個引數指定給 data
位置參數(Positional parameter),位置參數代表著它必須遵照參數定義的位置來指定;Dart 支援命名參數(Named parameter)。
Dart 函式的參數語法可以參考〈https://dart.dev/guides/language/language-tour#optional-parameters〉。
在 Text
看到的 'Hello, World!'
指定給位置參數 data
,而 textAlign
與 textDirection
就是命名參數,就 Text
在繪製元素時,textDirection
是必要資訊,textAlign
不指定是沒關係,不過就會跟時間顯示擠在一起:
基於某框架或平台撰寫程式,最重要的是文件到哪查,Flutter 的官方 API 文件是在〈Flutter - Dart API docs〉,而像是 Widget
、Text
這些 Widget
,可以點選頁面中 LIBRARIES 的 widgets。
當然,類別也是一大堆,可以善用頁面中的搜尋框,如果你想看類別的原始碼,可以在類別的說明頁面中,按一下右上角的藍色小 Icon(View source code)。