Flutter 構建一個 todo list 應用

語言: CN / TW / HK

我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第3篇文章,點選檢視活動詳情

今天,我們將使用 Flutter 構建一個動態的 todo list 的應用。

開發完成的效果如下:

demo.gif

我們直接進入正題。

基礎 Flutter 應用腳手架

```dart

create new project

flutter create flutter_todo_app

navigate to project

cd flutter_todo_app

run flutter

flutter run ```

我們清除檔案 lib/main.dart,從頭開始開發。

main.dart 這個檔案是 Flutter 應用的入口檔案。在這篇文章中,我將僅僅使用這個檔案來開發。

首先,我們先匯入 material 包。

dart import 'package:flutter/material.dart';

下一步,我們得有一個主要的方法。在這個例子中,它將返回 TodoApp 例項。

dart void main() => runApp( new TodoApp(), );

這個 TodoApp 應該是一個 statelessWidget。這將會是我們列表的骨架

dart class TodoApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Todo list', home: new TodoList(), ); } }

正如你所見,我返回了一個 MaterialApp 例項,它具有一個 title 屬性和一個 home 功能。這個 home 函式返回一個 TodoList 例項。這個 TodoList 類才是我們控制的列表項。

dart class TodoList extends StatefulWidget { @override _TodoListState createState() => new _TodoListState(); }

等等,這是什麼?所有的掛件都會呼叫一個狀態去知道將要發生什麼和渲染什麼。在這個例子中,我們呼叫了 _TodoListState。這將包含應用中的列表及其執行邏輯。

```dart class _TodoListState extends State { final TextEditingController _textFieldController = TextEditingController(); final List _todos = [];

@override Widget build(BuildContext context) { // Widget template comes here }

// Other functions } ```

接下來,建立列表變數。

dart final List<Todo> _todos = <Todo>[];

也許你已經注意到了,我們定義了這個列表的型別是 Todo,但 Flutter 怎麼知道 Todo 長是什麼樣呢?

Flutter 並不會知道,所以我們得建立一個類來定義。如下:

dart class Todo { Todo({required this.name, required this.checked}); final String name; bool checked; }

這跟 typescript 中的型別定義很像。我們告訴 flutter 一個 todo 項應該包含什麼,什麼欄位是必須的。在我們的案例中,我們有名字和 checked 兩個狀態屬性。

回到 _TodoListState 中,我們開始讓我們的掛件展示點東西。

dart @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Todo list'), ), body: ListView( padding: EdgeInsets.symmetric(vertical: 8.0), children: _todos.map((Todo todo) { return TodoItem( todo: todo, onTodoChanged: _handleTodoChange, ); }).toList(), ), floatingActionButton: FloatingActionButton( onPressed: () => _displayDialog(), tooltip: 'Add Item', child: Icon(Icons.add)), ); }

讓我們看看上面發生了什麼。我們返回了應用的一個腳手架,在腳手架上,我們添加了一個包含標題的 appBar 的屬性。我們定義了 body 屬性,這將存放 ListView 元件。

在上面程式碼片段中,通過 map 方法返回每個元素的 TodoItem

然後,在應用的底部,我們定義了一個按鈕。當按鈕被點選時候,將呼叫 _displayDialog 方法。

到目前為止,我們還需要完成下面的程式碼片段:

  • 建立 TodoItem
  • 定義一個 _displayDialog 函式
  • 定義一個 _handleTodoChange 函式

讓我們一個一個來解決。

建立 TodoItem

TodoItem 是我們列表項的單獨體現。

```dart class TodoItem extends StatelessWidget { TodoItem({ required this.todo, required this.onTodoChanged, }) : super(key: ObjectKey(todo));

final Todo todo; final onTodoChanged;

TextStyle? _getTextStyle(bool checked) { if (!checked) return null;

return TextStyle(
  color: Colors.black54,
  decoration: TextDecoration.lineThrough,
);

}

@override Widget build(BuildContext context) { return ListTile( onTap: () { onTodoChanged(todo); }, leading: CircleAvatar( child: Text(todo.name[0]), ), title: Text(todo.name, style: _getTextStyle(todo.checked)), ); } } ```

正如你所見,我們傳遞一個 todoonTodoChanged 進來。

然後我們定義了一個 TextStyle 去處理列表項是否被勾選。

然後我們使用 ListTile 掛件來展示內容和新增點選事件。

展示 Dialog 去新增列表項

點選應用的右下角的按鈕,將會調起 _displayDialog 方法。

這將調起一個帶有文字框的對話方塊。當點選確認的時候,將以文字框的內容基礎新增一個新的列表項。

_TodoListState 中建立 _displayDialog

dart Future<void> _displayDialog() async { return showDialog<void>( context: context, barrierDismissible: false, // user must tap button! builder: (BuildContext context) { return AlertDialog( title: const Text('Add a new todo item'), content: TextField( controller: _textFieldController, decoration: const InputDecoration(hintText: 'Type your new todo'), ), actions: <Widget>[ TextButton( child: const Text('Add'), onPressed: () { Navigator.of(context).pop(); _addTodoItem(_textFieldController.text); }, ), ], ); }, ); }

Flutter 中的 Future 表明在將來的某個時候將返回潛在的值或者錯誤資訊。在我們的案例中,將會返回使用者輸入的值。

對話方塊中有一個動作,就是當我們點選按鈕的時候,將會關閉對話方塊並且呼叫 _addTodoItem 函式。

我們看看 _addTodoItem 函式長什麼樣:

dart void _addTodoItem(String name) { setState(() { _todos.add(Todo(name: name, checked: false)); }); _textFieldController.clear(); }

這函式比你想象中的簡單,是吧。

列表項新增狀態

最後一部分是,我們應該為列表項進行標記。我們需要一個處理函式 _handleTodoChange

dart void _handleTodoChange(Todo todo) { setState(() { todo.checked = !todo.checked; }); }

這裡我們只是改變了其列表項的狀態。

完整的程式碼如下:

```dart // lib/main.dart import 'package:flutter/material.dart';

class Todo {   Todo({required this.name, required this.checked});   final String name;   bool checked; }

class TodoItem extends StatelessWidget {   TodoItem({     required this.todo,     required this.onTodoChanged,   }) : super(key: ObjectKey(todo));

final Todo todo;   final onTodoChanged;

TextStyle? _getTextStyle(bool checked) {     if (!checked) return null;     return TextStyle(       color: Colors.black54,       decoration: TextDecoration.lineThrough,     );

}

@override   Widget build(BuildContext context) {     return ListTile(       onTap: () {         onTodoChanged(todo);       },       leading: CircleAvatar(         child: Text(todo.name[0]),       ),       title: Text(todo.name, style: _getTextStyle(todo.checked)),     );   } }

class TodoList extends StatefulWidget {   @override   _TodoListState createState() => new _TodoListState(); }

class _TodoListState extends State {   final TextEditingController _textFieldController = TextEditingController();   final List _todos = [];

@override   Widget build(BuildContext context) {     return new Scaffold(       appBar: new AppBar(         title: new Text('Todo list'),       ),       body: ListView(         padding: EdgeInsets.symmetric(vertical: 8.0),         children: _todos.map((Todo todo) {           return TodoItem(             todo: todo,             onTodoChanged: _handleTodoChange,           );         }).toList(),       ),       floatingActionButton: FloatingActionButton(           onPressed: () => _displayDialog(),           tooltip: 'Add Item',           child: Icon(Icons.add)),     );   }

void _handleTodoChange(Todo todo) {     setState(() {       todo.checked = !todo.checked;     });   }

void _addTodoItem(String name) {     setState(() {       _todos.add(Todo(name: name, checked: false));     });     _textFieldController.clear();   }

Future _displayDialog() async {     return showDialog(       context: context,       barrierDismissible: false, // user must tap button!       builder: (BuildContext context) {         return AlertDialog(           title: const Text('Add a new todo item'),           content: TextField(             controller: _textFieldController,             decoration: const InputDecoration(hintText: 'Type your new todo'),           ),           actions: [             TextButton(               child: const Text('Add'),               onPressed: () {                 Navigator.of(context).pop();                 _addTodoItem(_textFieldController.text);               },             ),           ],         );       },     );   } }

class TodoApp extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new MaterialApp(       title: 'Todo list',       home: new TodoList(),     );   } }

void main() => runApp(new TodoApp()); ```

本文采用的是意譯的方式。原文連結 - Build a todo list app with Flutter

推薦閱讀