Flutter 构建一个 todo list 应用
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情
今天,我们将使用 Flutter
构建一个动态的 todo list
的应用。
开发完成的效果如下:
我们直接进入正题。
基础 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
@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)), ); } } ```
正如你所见,我们传递一个 todo
和 onTodoChanged
进来。
然后我们定义了一个 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
@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
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
推荐阅读
- 前端开发中 5 个很赞的资源
- 懂点心理学 - 马太效应
- Flutter 构建一个 todo list 应用
- Dart 知识点 - 数据类型
- Dart 知识点 - 混入 Mixin
- Dart 知识点 - 集合 List, Set, Map
- Flutter - 使用 push(), pop() 和路由进行导航
- Dart 知识点 - 面向对象基础
- Flutter: Stateful 挂件 vs Stateless 挂件
- Flutter 实现登录 UI
- Dart 知识点 - 抽象类和接口
- 自 2020 年以来全球的开源商业化软件融资情况
- IstioCon 2022 回顾及录像、PPT 分享
- 网页实现 1CM 物理长度
- Flutter 开发出现的那些 Bugs 和解决方案「持续更新... 」
- 仿写新闻客户端
- Beyond Istio OSS —— Istio 服务网格的现状及未来
- 在外企的工作生活「年中总结」
- 如何在 Istio 中集成 SPRIRE?
- Javascript尾递归编程