- 原文地址:Making a Todo App with Flutter
- 原文做者:Gearóid M
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:DateBro
- 校對者:geniusq1981
Flutter 是 Google 對 React Native 的迴應,它容許開發人員爲 Android 和 iOS 建立原生應用。與用 JSX 編寫的 React Native 不一樣,Flutter 應用程序是用 Google 的 Dart 語言編寫的.前端
Flutter 仍處於技術測試階段,但它的工具很是穩定,並提供了流暢的開發體驗。android
在這篇文章中,我將講解如何使用 Flutter 建立一個簡單的待辦事項應用程序。ios
這些說明是爲 MacOS 和 Linux 編寫的。Windows 須要一些額外的準備,所以請按照 Flutter Windows 指南進行操做,而後轉到下一步,建立應用程序。git
首先,下載下載與你的平臺匹配的 Flutter SDK。對於這個應用程序,咱們將在主目錄中建立一個名爲 dev
的目錄,並在那裏解壓 Flutter SDK。github
mkdir ~/dev
cd ~/dev
unzip ~/Downloads/flutter_macos_v0.3.2-beta.zip
複製代碼
如今咱們能夠在命令行裏使用 ~dev/flutter/bin/flutter 命令運行 Flutter。輸入命令有一些不夠優雅,因此讓咱們把它加到 $PATH 中。在 ~/.bashrc
文件的末尾添加這一行。web
export PATH=~/dev/flutter/bin:$PATH
複製代碼
而後運行 source~/.bashrc
以確保此更改生效。如今你能夠直接從命令行運行 flutter
命令了。設置完成後,咱們須要檢查以確保咱們已經安裝了應用程序開發所需的全部其餘內容,例如 Android Studio ,Xcode(僅限 MacOS)和其餘依賴。幸運的是,Flutter 附帶了一個工具,能夠很容易地檢查這個。只須要運行:macos
flutter doctor
複製代碼
這將準確地告訴你爲了正確運行 Flutter,須要安裝什麼。按照 flutter doctor 的說明,確保全部都已經正確安裝,而後再繼續下一步。windows
咱們將建立咱們的應用程序並在 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 應用程序更有趣,而且包含一些交互性。點擊右下角的按鈕屏幕中間的計數器數值會增大。
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 給開發增長了如此有用的功能。你能夠隨時調整代碼並進行測試,但不須要在每次進行更改後強制返回應用程序的初始界面。
你能夠看到一個標題已更改的 Flutter 的 「Hello World」 應用程序。
既然咱們知道了如何運行 Flutter 應用程序,那麼就該開始編寫本身的應用程序了。咱們選擇經典的待辦事項應用程序做爲例子。如上所述,咱們將使用 Dart 。它確定不是最著名的語言,但若是你以前使用過 Javascript(特別是 ES2015+),C++ 或 Java,那你將會以爲很是熟悉。
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
—— 注意它是大寫的,與熱重載不一樣。你將看到一個帶標題欄的簡單應用程序。
Android(Pixel 2)與 iOS(iPhone X)上標題欄樣式的區別
爲了使咱們的應用看起來更像一個待辦事項應用程序,咱們應當展現一些任務。你可能已經注意到咱們上面的簡單應用程序是一個 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()
,它的構造函數是 build
,它會構建一個全新的帶有更新了的 TODO 事項的 widget這個應用程序的第二個界面,能夠容許用戶添加任務
若是用戶只能添加自動生成的項目,那麼這個應用程序就不是頗有用。讓咱們改變咱們的 +
按鈕的工做方式,讓用戶可以指定他們本身的任務。咱們但願它打開第二個界面,裏面有一個簡單的文本框,用戶能夠在裏面輸入本身的任務。
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)
),
)
);
}
)
);
}
複製代碼
用戶完成任務後,須要一種方法將其標記爲已完成並從列表中刪除。爲了簡單起見,咱們要在用戶點擊任務時顯示一個對話框,詢問他們是否要將事項標記爲完成。
一個要求用戶確認其任務完成與否的對話框
咱們將建立兩個新函數來實現它,_removeTodoItem
和 _promptRemoveTodoItem
。_buildTodoItem
也將被修改來處理用戶的點擊交互。看看下面的新代碼,看看你可否明白它的工做原理。後面我會詳細介紹。
// 與 _addTodoItem 很是相似,它會修改待辦事項的字符串數組,
// 並經過使用 setState 通知應用程序狀態已更改
void _removeTodoItem(int index) {
setState(() => _todoItems.removeAt(index));
}
// 顯示警告對話框,詢問用戶任務是否已完成
void _promptRemoveTodoItem(int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Mark "${_todoItems[index]}" as done?'),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () => Navigator.of(context).pop()
),
new FlatButton(
child: new Text('MARK AS DONE'),
onPressed: () {
_removeTodoItem(index);
Navigator.of(context).pop();
}
)
]
);
}
);
}
Widget _buildTodoList() {
return new ListView.builder(
itemBuilder: (context, index) {
if(index < _todoItems.length) {
return _buildTodoItem(_todoItems[index], index);
}
},
);
}
Widget _buildTodoItem(String todoText, int index) {
return new ListTile(
title: new Text(todoText),
onTap: () => _promptRemoveTodoItem(index)
);
}
複製代碼
首先,咱們須要從列表中刪除任務的功能,這能夠用 _removeTodoItem
函數來處理。最佳是經過 _todoItems
數組中的索引來引用咱們要刪除的項目。若是有多個具備相同名稱的任務,按名稱引用會出現問題。一旦咱們獲得了項目的索引,使用 Dart 的 removeAt
函數將其從數組中刪除就很簡單了。請記住,咱們須要將它包裝在 setState
中,以便在刪除項後從新呈現列表。
當用戶點擊它時,不該該當即刪除項目,而應該首先以更加 user-friendly 的方式提示他們。_promptRemoveTodoItem
函數使用 Flutter 的 AlertDialog
組件來執行這個操做。這個構造函數和咱們以前看到的相似,好比 Scaffold
。它只接受文本標題和按鈕數組。按鈕敲擊的處理由 onPressed
完成,若是按下正確的按鈕,就調用 _removeTodoItem
函數處理。
最後,咱們在 _buildTodoItem
中爲每一個列表項添加一個 onTap
處理程序,它會顯示上面的提示。咱們須要這個處理程序的 todo 項的索引,因此咱們還必須修改 _buildTodoList
以在調用 _buildTodoItem
時傳遞項的索引。Flutter 會自動添加 Material 風格的 tap 動畫,對用戶來講體驗不錯。
就像接下來的視頻演示的同樣,最終的 App 容許用戶添加和刪除待辦事項。若是你想使用的話,最終生成的 main.dart
文件能夠在 GitHub 上找到。
應用程序最終形態
若是您但願繼續使用它,能夠在應用程序中改進一些內容。好比,因爲 Flutter 的保存方式,用戶的待辦事項在應用程序啓動間隙保存,但這種保持用戶數據的方法並不可靠。不過,用戶的待辦事項能夠使用 shared_preferences 安全地保存在設備上。
想要進一步改進應用程序,你能夠更改主題,甚至爲用戶的待辦事項添加類別。
在這篇博文中,個人目的是向你們簡要介紹一下 Flutter 的潛力。若是你有興趣瞭解有關 Flutter 的更多信息,Flutter 開發者文檔很是全面、有用,最關鍵的是上面有不少示例。
儘管 Flutter 仍處於測試階段(寫做時爲 v0.3.2),但其生態已很是成熟。你不會發現本身找不到一些重要功能或文檔。Google Adwords 等主要應用已在生產中使用 Flutter,所以若是你開始開發新應用,Flutter 值得研究一下。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。