Flutter学习(三)——实践 Todo-list(1)

理论学习总是比较枯燥的,而且新接触一个东西的时候,如果都是理论学习很难让自己获得成就感和满足感,这样就很坚持下去了。

现在开始,来一个小小的实践练习,当将理论知识变成能看得到的画面的时候,成就感油然而生,给自己的理论学习增加动力。

准备工作

在终端执行命令,创建初始项目:

1
flutter create todolist

找到 main.dart 文件,将其中 _MyHomePageState 代码删减:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class _MyHomePageState extends State<MyHomePage> {
void _incrementTodo() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text("Placeholder"),
floatingActionButton: FloatingActionButton(
onPressed: _incrementTodo,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

数据准备

首先,我们需要每一行显示的数据,包含了:标题、描述和是否选中的状态三个数据。我们为此创建模型:

1
2
3
4
5
6
7
8
9
10
11
class TodoModel {
final String title;
final String subTitle;
bool isChecked;

TodoModel({
required this.title,
required this.subTitle,
this.isChecked = false
});
}

然后,再来一个数组容器装着前面创建的模型数据,给列表提供数据就可以了。

变量以下划线 _ 开头,在 Dart 语言中使用下划线前缀标识符,会强制其变成私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class _MyHomePageState extends State<MyHomePage> {

final _todos = <TodoModel>[
TodoModel(title: "Todo_list Title", subTitle: "Todo_list Subtitle")
];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text("Placeholder")
);
}
}

创建列表

现在需要创建主体的列表了,这里新建一个方法,用来返回列表 ListView 的 Widget。这里使用了 ListView.separated 构造器,方便创建每个条目之间的分割线。

1
2
3
4
5
6
7
8
9
10
11
12
13
Widget _buildList() {
return ListView.separated(
separatorBuilder: (context, index) => const Divider(
thickness: 1,
height: 1,
color: Color.fromARGB(255, 232, 236, 236),
),
itemCount: _todos.length,
itemBuilder: (context, i) {
return _buildItem(_todos[i]);
}
);
}

itemBuilder 会根据传入的数量,从 0 开始迭代并调用 itemBuilder 属性中传入的函数。这个函数返回每一行显示的 Item。

创建显示的 Item

同样的,新建一个方法 _buildItem 用来返回每一行 Item 显示的 Widget。这里 Todo 的显示样式,直接使用了系统提供的 CheckboxListTile 来显示。接下来开始编写这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Widget _buildItem(TodoModel model) {
return SizedBox(
height: 60,
child: CheckboxListTile(
value: model.isChecked,
title: Text(
model.title,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.w400
),
),
subtitle: Text(
model.subTitle,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.grey,
fontSize: 14,
fontWeight: FontWeight.normal
),
),
// activeColor: Colors.red,
dense: true,
isThreeLine: false,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
model.isChecked = value!;
});
Future.delayed(const Duration(seconds: 1), (){
setState(() {
_todos.remove(model);
});
});
}
)
);
}

在 CheckboxListTile 中,需要显示的数据都是来源于 _todos 的模型数据。在 CheckboxListTile 的 onChanged 的方法中,更改了当前 model 的数据,并刷新当前页面显示状态。并利用 Future 的 delayed 方法,延时 1s 将当前 勾选的 Todo 从列表中删除。

新增 Todo

这里直接使用项目初始化时,模板工程中的 FloatingActionButton 就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class _MyHomePageState extends State<MyHomePage> {

final _todos = <TodoModel>[
TodoModel(title: "Todo_list Title", subTitle: "Todo_list Subtitle")
];

int _count = 0;

void _incrementTodo() {
setState(() {
_counter++;

// 将当前时间转化为字符串
var date = DateTime.now();
String createTime = "${date.year.toString()}-${date.month.toString()}-${date.day.toString()} ${date.hour.toString()}:${date.minute.toString()}";
_todos.add(TodoModel(title: "Todo_list Title - add $_count", subTitle: "create at $createTime"));
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text("Placeholder"),
floatingActionButton: FloatingActionButton(
onPressed: _incrementTodo,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

组装完成

更新一下 _MyHomePageState 的 build 方法。将新建的列表创建方法传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class _MyHomePageState extends State<MyHomePage> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildList(),
floatingActionButton: FloatingActionButton(
onPressed: _incrementTodo,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
...
}

好了,现在所有步骤都已经完成了,保存并重新运行程序,就可以得到一个和开篇时一样的项目了。