這将準确地告訴你為了正确運作 Flutter,需要安裝什麼。按照 flutter doctor 的說明,確定所有都已經正确安裝,然後再繼續下一步。
建立一個應用程式
我們将建立我們的應用程式并在 Android 上進行測試,因為這在所有作業系統上都可以完成,是以這些步驟對于 iOS 都是一樣的。
Flutter 為不少 IDE 提供插件,包括 Android Studio 和 Visual Studio Code。但是,對于我們簡單的應用程式來說,我們完全可以使用指令行和一個簡單的文本編輯器完成所有操作。首先,讓我們建立我們的應用程式,我們将其稱為
flutter_todo
。
flutter create flutter_todo
Flutter 中這個指令可以建立一個簡單的 “Hello World” 風格的應用程式。我們可以在 Android 模拟器中立即測試它。打開 Android Studio,Flutter Doctor 會幫助你進行設定。這裡我們要建立一個模拟器,但 Android Studio 要求我們先建立一個項目。是以,讓我們使用新建立的 Flutter 項目。選擇
導入項目(Gradle,Eclipse ADT 等)
,然後選擇檔案夾
~/dev/flutter_todo/android
。完成導入項目後,檢查控制台中是否有錯誤。如果有,使用 Android Studio 修複它們。
現在,我們可以通過
Tools> Android> AVD Manager
來建立模拟器。單擊“建立虛拟裝置”,選擇 Pixel,然後一路選擇預設值,直到建立完畢。現在,你可以在清單中看到新裝置 —— 輕按兩下啟動它。模拟器運作後,就可以在上面運作我們的 Flutter 應用程式了。
cd flutter_todo
flutter run
這個應用程式比普通的 Hello World 應用程式更有趣,并且包含一些互動性。點選右下角的按鈕螢幕中間的計數器數值會增大。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-OuuF66o0-1630675957558)(https://user-gold-cdn.xitu.io/2018/7/18/164addfc43fbcb4a?imageView2/0/w/1280/h/960/ignore-error/1)]
Flutter 的 “Hello World” 應用程式
熱重載
Flutter 有一個非常有用的熱重載功能,就像 React Native 一樣。這意味着每次改代碼時都不需要重新建構和重新運作應用程式。我們來看看它是如何工作的。
比如我們想要更改 Hello World 應用程式标題欄中的文本。所有代碼都位于
lib/main.dart
中。在這個檔案中,找到下面這行:
home: new MyHomePage(title: 'Flutter Demo Home Page'),
然後替換為:
home: new MyHomePage(title: 'Basic Flutter App'),
儲存檔案,然後傳回運作
flutter run
的指令行。你需要做的就是輸入
r
,這會啟動熱重載過程。你會注意到在模拟器中,标題已經更改。不僅如此,如果你之前點選過按鈕,你會發現到計數器并沒有重置成 0。就是這個 stateful hot reload 給開發增加了如此有用的功能。你可以随時調整代碼并進行測試,但不需要在每次進行更改後強制傳回應用程式的初始界面。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-niWLTfjY-1630675957561)(https://user-gold-cdn.xitu.io/2018/7/18/164addfc6b1ac633?imageView2/0/w/1280/h/960/ignore-error/1)]
你可以看到一個标題已更改的 Flutter 的 “Hello World” 應用程式。
Flutter 基礎
既然我們知道了如何運作 Flutter 應用程式,那麼就該開始編寫自己的應用程式了。我們選擇經典的待辦事項應用程式作為例子。如上所述,我們将使用 Dart 。它肯定不是最著名的語言,但如果你之前使用過 Javascript(特别是 ES2015+),C++ 或 Java,那你将會覺得非常熟悉。
Material Design
Flutter 附帶一個軟體包,可以幫助快速制作 Material 風格的 App。它提供了一種建立帶标題欄和正文的螢幕的簡單方法。讓我們首先設定一下待辦事項應用程式,使它有一個我們應用程式名稱的标題欄。
删除
lib/main.dart
中現有的所有代碼,并添加以下内容:
// 導入 MaterialApp 和其他元件,我們可以使用它們來快速建立 Material 應用程式
import 'package:flutter/material.dart';
// 用 Dart 編寫的代碼從主函數開始執行,runApp 是 Flutter 的一部分,而且需要元件作為我們 app
// 的容器。在 Flutter 中,
// 萬物皆元件。
void main() => runApp(new TodoApp());
// Flutter 中,萬物皆元件,甚至是整個 App 本身
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Todo List',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Todo List')
)
),
);
}
}
元件
這一小段代碼顯示了 Flutter 中一個重要的概念 —— 萬物皆元件。我們的整個應用程式是一個元件,其中包含
MaterialApp
元件。
Scaffold
是一個元件,它可以幫助我們快速建立合适的 Material 布局,而不用擔心手動設定樣式。
AppBar
是一個接受标題的元件,它會在螢幕頂部建立一個欄,這在應用程式中很常見。在 Android 上,它會将文本左側對齊,而在 iOS 上,它會将文本居中。
由于我們對應用程式進行了比較大的改動,是以這次熱重載将無法正常工作。這次我們需要完全重新開機應用程式。在指令行中,輸入
R
—— 注意它是大寫的,與熱重載不同。你将看到一個帶标題欄的簡單應用程式。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-gx8RNGdD-1630675957563)(https://user-gold-cdn.xitu.io/2018/7/18/164addfc9eddd1ab?imageView2/0/w/1280/h/960/ignore-error/1)]
Android(Pixel 2)與 iOS(iPhone X)上标題欄樣式的差別
Stateless Widgets 和 Stateful Widgets
為了使我們的應用看起來更像一個待辦事項應用程式,我們應當展示一些任務。你可能已經注意到我們上面的簡單應用程式是一個
StatelessWidget
。這意味着無法動态修改。對于我們的待辦事項應用程式來說,這并不好,因為待辦事項會一直添加和删除。但是,
StatelessWidget
可以生成動态的子項,它們是
StatefulWidget
。讓我們從整個 app 容器開始分析我們的有狀态功能(待辦事項清單容器)。
要建立一個 stateful widget,我們需要兩個類 —— 一個用于元件本身,另一個用于建立狀态。這個設定允使我們可以輕松儲存狀态,并能夠使用熱重載等功能。
為什麼一個 stateful widget 需要兩個類? 想象一下,我們有一個待辦事項清單元件,裡面有五個待辦事項。當我們往清單中添加另一個事項時,Flutter 會以不同的方式更新螢幕。你可能希望它隻是将這一項添加到現有元件中。實際上,它建立了一個全新的元件,并把它同舊的元件進行比較,以确定在螢幕上進行哪些更改。
由于我們在每次更改時都會建立一個新視窗元件,是以我們無法在視窗元件中存儲任何狀态,因為它會在下一次更改時丢失。這就是為什麼我們需要一個單獨的 State 類。
下面的代碼顯示了我們新的有狀态應用。它在功能上與我們之前的代碼相同,但現在可以輕松更改待辦事項清單的内容了。用下面的代碼替換
lib/main.dart
的所有内容,并用
R
完全重新開機。
// 導入 MaterialApp 和其他元件,我們可以使用它們來快速建立 Material 應用程式
import 'package:flutter/material.dart';
void main() => runApp(new TodoApp());
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Todo List',
home: new TodoList()
);
}
}
class TodoList extends StatefulWidget {
@override
createState() => new TodoListState();
}
class TodoListState extends State<TodoList> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Todo List')
)
);
}
}
修改狀态
現在我們的應用已準備好變得有狀态化,我們希望能夠添加待辦事項。首先,我們要添加一個浮動操作按鈕(FAB),它可以添加一個自動生成的任務。一會兒我們會允許使用者輸入自己的任務。我們所有的更改都在
TodoListState
中。
class TodoListState extends State<TodoList> {
List<String> _todoItems = [];
// 每按一次 + 按鈕,都會調用這個方法
void _addTodoItem() {
// Putting our code inside "setState" tells the app that our state has changed, and
// it will automatically re-render the list
setState(() {
int index = _todoItems.length;
_todoItems.add('Item ' + index.toString());
});
}
// 建構整個待辦事項清單
Widget _buildTodoList() {
return new ListView.builder(
itemBuilder: (context, index) {
// itemBuilder 将被自動調用,因為清單需要多次填充其可用空間
// 而這很可能超過我們擁有的待辦事項數量。
// 是以,我們需要檢查索引是否正确。
if(index < _todoItems.length) {
return _buildTodoItem(_todoItems[index]);
}
},
);
}
// 建構一個待辦事項
Widget _buildTodoItem(String todoText) {
return new ListTile(
title: new Text(todoText)
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Todo List')
),
body: _buildTodoList(),
floatingActionButton: new FloatingActionButton(
onPressed: _addTodoItem,
tooltip: 'Add task',
child: new Icon(Icons.add)
),
);
}
}
讓我們仔細看看如何添加一個新的待辦事項:
- 使用者點選
按鈕,回調+
函數,進而觸發onPressed
函數_addTodoItem
-
向addTodoItem
數組中添加一個新的字元串_todoItems
-
中的所有内容都會被包含在_addTodoItem
調用中,這個調用會通知應用程式待辦事項清單已更新setState
-
會在待辦事項清單更新時被觸發TodoList.createState
- 這會調用
,它的構造函數是new TodoListState()
,它會建構一個全新的帶有更新了的 TODO 事項的 widgetbuild
- 該應用程式擷取此新視窗元件,将其與前一個視窗元件進行比較,并添加新項目而不更改其他項目
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-WPqBQSOQ-1630675957566)(https://user-gold-cdn.xitu.io/2018/7/18/164addfc4f2a9a36?imageView2/0/w/1280/h/960/ignore-error/1)]
這個應用程式的第二個界面,可以允許使用者添加任務
使用者互動
如果使用者隻能添加自動生成的項目,那麼這個應用程式就不是很有用。讓我們改變我們的
+
按鈕的工作方式,讓使用者能夠指定他們自己的任務。我們希望它打開第二個界面,裡面有一個簡單的文本框,使用者可以在裡面輸入自己的任務。
添加待辦事項
Flutter 可以非常簡單地使用
MaterialPageRoute
元件添加第二個界面。這需要一個
builder
函數作為參數。這将傳回一個“Scaffold”,你可以從我們現有的界面中認出它。是以,建立第二個界面的布局将和第一個界面相同。
建立頁面後,我們需要告訴應用程式如何使用它,并且它應該在另一個界面的頂部觸發動畫。Flutter 為我們提供了
Navigator
來完成這項工作,它使用了在移動應用程式中很常見的 navigation stack 概念。要添加新螢幕,我們把他 push 到導航堆棧。要删除它,我們就 pop 它。我們會建立一個名為
_pushAddTodoScreen
的新函數,它将處理所有這些任務。然後我們可以修改
floatingActionButton
的
onPressed
方法來調用這個函數。
用下面的代碼替換現有的
_addTodoItem
和
build
函數,并在它們旁邊添加新的
_pushAddTodoScreen
函數。按
R
觸發完全重新開機,以確定删除上次自動生成的任務。單擊
+
按鈕并添加任務,然後按鍵盤上的 Enter 鍵。螢幕将會關閉,任務會出現在清單中。
// 添加待辦事項現在接受一個字元串,而不是自動生成
void _addTodoItem(String task) {
// 僅在使用者實際輸入内容時添加任務
if(task.length > 0) {
setState(() => _todoItems.add(task));
}
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Todo List')
),
body: _buildTodoList(),
floatingActionButton: new FloatingActionButton(
onPressed: _pushAddTodoScreen, // pressing this button now opens the new screen
tooltip: 'Add task',
child: new Icon(Icons.add)
),
);
}
void _pushAddTodoScreen() {
// 将此頁面推入任務棧
Navigator.of(context).push(
// MaterialPageRoute 會自動為螢幕條目設定動畫
// 并添加後退按鈕以關閉它
new MaterialPageRoute(
builder: (context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Add a new task')
),
body: new TextField(
autofocus: true,
onSubmitted: (val) {
_addTodoItem(val);
Navigator.pop(context); // Close the add todo screen
},
decoration: new InputDecoration(
hintText: 'Enter something to do...',
contentPadding: const EdgeInsets.all(16.0)
),
)
);
}
)
);
}
删除待辦事項
使用者完成任務後,需要一種方法将其标記為已完成并從清單中删除。為了簡單起見,我們要在使用者點選任務時顯示一個對話框,詢問他們是否要将事項标記為完成。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-SDK0nl4w-1630675957567)(https://user-gold-cdn.xitu.io/2018/7/18/164addfc4f4af573?imageView2/0/w/1280/h/960/ignore-error/1)]
一個要求使用者确認其任務完成與否的對話框
我們将建立兩個新函數來實作它,
_removeTodoItem
和
_promptRemoveTodoItem
。
_buildTodoItem
也将被修改來處理使用者的點選互動。看看下面的新代碼,看看你能否明白它的工作原理。後面我會詳細介紹。
删除待辦事項
使用者完成任務後,需要一種方法将其标記為已完成并從清單中删除。為了簡單起見,我們要在使用者點選任務時顯示一個對話框,詢問他們是否要将事項标記為完成。
[外鍊圖檔轉存中…(img-SDK0nl4w-1630675957567)]
一個要求使用者确認其任務完成與否的對話框
我們将建立兩個新函數來實作它,
_removeTodoItem
和
_promptRemoveTodoItem
。
_buildTodoItem
也将被修改來處理使用者的點選互動。看看下面的新代碼,看看你能否明白它的工作原理。後面我會詳細介紹。