[譯] 用 Flutter 寫一個待辦事項應用

DateBro發表於2018-07-18

[譯] 用 Flutter 寫一個待辦事項應用

Flutter 是 Google 對 React Native 的回應,它允許開發人員為 Android 和 iOS 建立原生應用。與用 JSX 編寫的 React Native 不同,Flutter 應用程式是用 Google 的 Dart 語言編寫的.

Flutter 仍處於技術測試階段,但它的工具非常穩定,並提供了流暢的開發體驗。

在這篇文章中,我將講解如何使用 Flutter 建立一個簡單的待辦事項應用程式。

安裝相關工具

這些說明是為 MacOS 和 Linux 編寫的。Windows 需要一些額外的準備,因此請按照 Flutter Windows 指南進行操作,然後轉到下一步,建立應用程式

首先,下載下載與你的平臺匹配的 Flutter SDK。對於這個應用程式,我們將在主目錄中建立一個名為 dev 的目錄,並在那裡解壓 Flutter SDK。

mkdir ~/dev
cd ~/dev
unzip ~/Downloads/flutter_macos_v0.3.2-beta.zip
複製程式碼

現在我們可以在命令列裡使用 ~dev/flutter/bin/flutter 命令執行 Flutter。輸入命令有一些不夠優雅,所以讓我們把它加到 $PATH 中。在 ~/.bashrc 檔案的末尾新增這一行。

export PATH=~/dev/flutter/bin:$PATH
複製程式碼

然後執行 source~/.bashrc 以確保此更改生效。現在你可以直接從命令列執行 flutter 命令了。設定完成後,我們需要檢查以確保我們已經安裝了應用程式開發所需的所有其他內容,例如 Android Studio ,Xcode(僅限 MacOS)和其他依賴。幸運的是,Flutter 附帶了一個工具,可以很容易地檢查這個。只需要執行:

flutter doctor
複製程式碼

這將準確地告訴你為了正確執行 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 應用程式更有趣,並且包含一些互動性。點選右下角的按鈕螢幕中間的計數器數值會增大。

[譯] 用 Flutter 寫一個待辦事項應用

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 寫一個待辦事項應用

你可以看到一個標題已更改的 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 —— 注意它是大寫的,與熱過載不同。你將看到一個帶標題欄的簡單應用程式。

[譯] 用 Flutter 寫一個待辦事項應用

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(),它的建構函式是 build,它會構建一個全新的帶有更新了的 TODO 事項的 widget
  • 該應用程式獲取此新視窗元件,將其與前一個視窗元件進行比較,並新增新專案而不更改其他專案

[譯] 用 Flutter 寫一個待辦事項應用

這個應用程式的第二個介面,可以允許使用者新增任務

使用者互動

如果使用者只能新增自動生成的專案,那麼這個應用程式就不是很有用。讓我們改變我們的 + 按鈕的工作方式,讓使用者能夠指定他們自己的任務。我們希望它開啟第二個介面,裡面有一個簡單的文字框,使用者可以在裡面輸入自己的任務。

新增待辦事項

Flutter 可以非常簡單地使用 MaterialPageRoute 元件新增第二個介面。這需要一個 builder 函式作為引數。這將返回一個“Scaffold”,你可以從我們現有的介面中認出它。因此,建立第二個介面的佈局將和第一個介面相同。

建立頁面後,我們需要告訴應用程式如何使用它,並且它應該在另一個介面的頂部觸發動畫。Flutter 為我們提供了 Navigator 來完成這項工作,它使用了在移動應用程式中很常見的 navigation stack 概念。要新增新螢幕,我們把他 push 到導航堆疊。要刪除它,我們就 pop 它。我們會建立一個名為 _pushAddTodoScreen 的新函式,它將處理所有這些任務。然後我們可以修改 floatingActionButtononPressed 方法來呼叫這個函式。

用下面的程式碼替換現有的 _addTodoItembuild 函式,並在它們旁邊新增新的 _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)
            ),
          )
        );
      }
    )
  );
}
複製程式碼

刪除待辦事項

使用者完成任務後,需要一種方法將其標記為已完成並從列表中刪除。為了簡單起見,我們要在使用者點選任務時顯示一個對話方塊,詢問他們是否要將事項標記為完成。

[譯] 用 Flutter 寫一個待辦事項應用

一個要求使用者確認其任務完成與否的對話方塊

我們將建立兩個新函式來實現它,_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 寫一個待辦事項應用

應用程式最終形態

如果您希望繼續使用它,可以在應用程式中改進一些內容。比如,由於 Flutter 的儲存方式,使用者的待辦事項在應用程式啟動間隙儲存,但這種保持使用者資料的方法並不可靠。不過,使用者的待辦事項可以使用 shared_preferences 安全地儲存在裝置上。

想要進一步改進應用程式,你可以更改主題,甚至為使用者的待辦事項新增類別。

繼續瞭解 Flutter

在這篇博文中,我的目的是向大家簡要介紹一下 Flutter 的潛力。如果你有興趣瞭解有關 Flutter 的更多資訊,Flutter 開發者文件非常全面、有用,最關鍵的是上面有很多示例。

儘管 Flutter 仍處於測試階段(寫作時為 v0.3.2),但其生態已非常成熟。你不會發現自己找不到一些重要功能或文件。Google Adwords 等主要應用已在生產中使用 Flutter,因此如果你開始開發新應用,Flutter 值得研究一下。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章