功能
- 添加todo item
- 删除todo item
- 搜索todo item
- 完成todo item 播放对应的音效,提示完成所花的时间。
- 菜单栏功能没实现,读者可以自己动手进行修改
- 背景图片,用户可以替换assets里面的照片
- 右上角的头像,用户可以替换assets里面的照片
- 增加toast功能,空item不允许添加
代码
配置文件
先看配置文件,主要是加入了一些额外的包,just_audio
为了制作声音,toast
是为了做提示。加入之后命令行输入flutter pub install
。如果是vs code的话,会自动下载
name: m_todo_list
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: '>=2.19.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
just_audio: ^0.9.33
toast: ^0.3.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/music/
UI
如图整个项目的ui可以分为四个部分:分别为AppBar,SearchBox,TodoItem list,和最下面的添加框。
这下面的代码就是主界面的布局了。
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: tdBGColor,
appBar: _buildAppBar(), // 1. appBar
body: Stack( // 2.3.4
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Column(children: [
searchBox(), // 2. searchbox
Expanded( // 3. listview
child: ListView(
children: [
Container(
margin: EdgeInsets.only(
top: 50,
bottom: 20,
),
child: Text(
"All ToDos",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w500,
),
),
),
for (ToDo todoo in _foundToDo.reversed)
ToDoItem(
todo: todoo,
onToDoChanged: _handleToDoChange,
onDeleteItem: _deleteToDoItem,
)
],
))
]),
),
Align( // 4. add item box
alignment: Alignment.bottomCenter,
child: Row(children: [
Expanded(
child: Container(
margin: EdgeInsets.only(
bottom: 20,
right: 20,
left: 20,
),
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 0.0),
blurRadius: 10.0,
spreadRadius: 0.0,
)
],
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: _todoController,
decoration: InputDecoration(
hintText: "Add a new todo item",
border: InputBorder.none,
),
onSubmitted: (value) {
_addToDoItem(value);
},
),
)),
Container(
margin: EdgeInsets.only(
bottom: 20,
right: 20,
),
child: ElevatedButton(
onPressed: () {
_addToDoItem(_todoController.text);
},
style: ElevatedButton.styleFrom(
backgroundColor: tdBlue,
minimumSize: Size(60, 60),
elevation: 10,
),
child: Text(
"+",
style: TextStyle(fontSize: 40),
),
),
)
]),
)
],
),
);
}
AppBar
左边一个menu的Icon,右边一个图片,SizeBox包裹,borderRadius做成圆角。MainAxisAlignment.spaceBetween把两边分开。背景颜色在constants/colors.dart文件夹定义了。
AppBar _buildAppBar() {
return AppBar(
backgroundColor: tdBGColor,
elevation: 0,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.menu,
color: tdBlack,
size: 30,
),
SizedBox(
width: 50,
height: 50,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.asset("assets/images/nana3.png"),
))
],
),
);
}
SearchBox
这里定义了一个TextField,然后设置了输入框的prefixIcon同时去掉了border,添加提示文字hintText。这里加入了一个onChanged事件,这里后面会继续提到,现在只讲ui这部分。
Widget searchBox() {
return Container(
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: TextField(
onChanged: (value) => _runFilter(value),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 10),
prefixIcon: Icon(
Icons.search,
color: tdBlack,
size: 20,
),
prefixIconConstraints: BoxConstraints(
maxHeight: 20,
maxWidth: 25,
),
border: InputBorder.none,
hintText: "Search...",
)),
);
}
listview
上边是个标题文字,下面输出的是我们定义的todoItems。
Expanded(
child: ListView(
children: [
Container(
margin: EdgeInsets.only(
top: 50,
bottom: 20,
),
child: Text(
"All ToDos",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w500,
),
),
),
for (ToDo todoo in _foundToDo.reversed)
ToDoItem(
todo: todoo,
onToDoChanged: _handleToDoChange,
onDeleteItem: _deleteToDoItem,
)
],
))
ToDo的数据结构定义如下:
class ToDo {
String id;
String todoText;
bool isDone;
ToDo({
required this.id,
required this.todoText,
this.isDone = false,
});
// 定义静态方法,可以直接通过class name 进行调用,即ToDo.todoList()
static List<ToDo> todoList() {
return [
ToDo(id: '01', todoText: "Hello, Welcome to use this app.", isDone: true),
ToDo(id: '02', todoText: "Have fun!", isDone: false),
];
}
}
ToDoItem的Widget的定义如下,一个ListTitle,定义了leading和trailing,中间是展示的文本。
class ToDoItem extends StatelessWidget {
final ToDo todo;
final onToDoChanged;
final onDeleteItem;
const ToDoItem({
Key? key,
required this.todo,
required this.onToDoChanged,
required this.onDeleteItem,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20),
child: ListTile(
onTap: () {
onToDoChanged(todo);
},
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
tileColor: Colors.white,
leading: Icon(
todo.isDone ? Icons.check_box : Icons.check_box_outline_blank,
color: tdBlue,
),
title: Text(
todo.todoText,
style: TextStyle(
fontSize: 16,
color: tdBlack,
decoration: todo.isDone ? TextDecoration.lineThrough : null,
),
),
trailing: Container(
padding: EdgeInsets.all(0),
margin: EdgeInsets.symmetric(vertical: 12),
height: 35,
width: 35,
decoration: BoxDecoration(
color: tdRed,
borderRadius: BorderRadius.circular(5),
),
child: IconButton(
color: Colors.white,
iconSize: 18,
icon: Icon(Icons.delete),
onPressed: () {
onDeleteItem(todo.id);
},
),
),
),
);
}
}
add button
定义了一个container,左边一个textfield,右边一个Container(ElevatedButton)。
Align(
alignment: Alignment.bottomCenter,
child: Row(children: [
Expanded(
child: Container(
margin: EdgeInsets.only(
bottom: 20,
right: 20,
left: 20,
),
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 0.0),
blurRadius: 10.0,
spreadRadius: 0.0,
)
],
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: _todoController,
decoration: InputDecoration(
hintText: "Add a new todo item",
border: InputBorder.none,
),
onSubmitted: (value) {
_addToDoItem(value);
},
),
)),
Container(
margin: EdgeInsets.only(
bottom: 20,
right: 20,
),
child: ElevatedButton(
onPressed: () {
_addToDoItem(_todoController.text);
},
style: ElevatedButton.styleFrom(
backgroundColor: tdBlue,
minimumSize: Size(60, 60),
elevation: 10,
),
child: Text(
"+",
style: TextStyle(fontSize: 40),
),
),
)
]),
)
相关链接
本项目开源的Github
Tutorial video
Introduction of text field
Github Reference
Music library
Flutter music library
Turn all widgets be tappable.
Background image