天天看點

[譯] 用 Flutter 寫一個待辦事項應用,看這裡

這将準确地告訴你為了正确運作 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()

    ,它的構造函數是

    build

    ,它會建構一個全新的帶有更新了的 TODO 事項的 widget
  • 該應用程式擷取此新視窗元件,将其與前一個視窗元件進行比較,并添加新項目而不更改其他項目

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(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

也将被修改來處理使用者的點選互動。看看下面的新代碼,看看你能否明白它的工作原理。後面我會詳細介紹。