根据之前学的控件,我们来撸一个最简单的待办事项app,我们先看下最终效果图:
首先我们在脑子里大概构思下这个app要实现什么功能,页面构成是什么。
第一步:构思这个app的主要功能:
1、可以通过用户自己点击按钮进行待办事项增加操作。
2、将用户的待办事项滚动列表的形式展现在页面上。
3、当用户点击某个事项后,弹出一个对话框询问是否标记成完成,如果是则删除该事项。
有了上面的大概构思,我们开始设计UI构成
页面UI主要包含三部分内容:
1、AppBar,用于显示标题。
2、中间内容显示区域,由一个ListView构成,用于展示用户添加的待办事项。
3、右下角一个圆形按钮,用于点击添加新的待办事项。
下面我们按照上面分析的先进行ui布局:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Todo Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new TodoList(),
);
}
}
class TodoList extends StatefulWidget {
@override
State<StatefulWidget> createState() => new TodoListState();
}
class TodoListState extends State<TodoList> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('待办清单'),
),
body: new Center(
child: new Text(
'点击按钮开始添加',
style: new TextStyle(fontSize: 20.0, color: Colors.grey),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: null,
tooltip: '新增待办事项',
child: new Icon(
Icons.add,
size: 25.0,
),
),
);
}
}
以上代码包含一个程序入口main函数,返回一个符合Material风格的app。其中home返回一个TodoList页面,该页面由脚手架包含的appbar、body、button组成。
尝试着运行一下,效果如下:
下面我们添加一个ListView用于展示待办事项清单:
//自动生成20个字符串构成一个数组
List<String> _todoItems = List.generate(20, (i) => 'item ${i}');
//ListView控件,用于滚动展示待办事项
Widget TodoListView() {
return new ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return TodoItem(index);
});
}
//ListTile控件,描绘每个待办事项
Widget TodoItem(int i) {
return new ListTile(
title: new Text(_todoItems[i]),
);
}
修改TodoListState中body部分内容
body: new Center(
child: _todoItems.length == 0 ? hintText() : TodoListView()),
//当待办事项记录是0的时候,显示'点击按钮开始添加'在屏幕作为提示语
Widget hintText() {
return new Text(
'点击按钮开始添加',
style: new TextStyle(fontSize: 20.0, color: Colors.grey),
);
}
尝试运行后效果如下:
你们肯定发现了,现在的事项是自动生成的,无法满足用户自己添加待办事项的需求,因此我们需要继续改造,让用户可以自己编辑添加事项。
我们通过点击右下角的添加按钮,跳转至添加待办事项页面,用户点击编辑框可以自己编辑内容,并保存结果。
我们添加一个编辑待办事项的页面:
//点击右下角的按钮后,将新的路由页面推入栈中,该页面包含一个文本编辑控件,用于用户编辑内容。
void _todoEdit() {
Navigator.push(context, new MaterialPageRoute(builder: (context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('添加待办事项'),
leading: new BackButton(),
),
body: new TextField(
decoration: new InputDecoration(
hintText: '编辑待办事项',
contentPadding: const EdgeInsets.all(10.0),
),
onSubmitted: (text) {
if (text.length == 0) {
Navigator.of(context).pop();
} else {
_todoItemsChanged(text);
Navigator.of(context).pop();
}
},
),
);
}));
}
修改TodoListState中floatingActionButton的onPressed内容:
floatingActionButton: new FloatingActionButton(
onPressed: () => _todoEdit(),
再次尝试运行程序,当点击屏幕右下角按钮后,页面跳转至编辑待办事项的页面 如图:
这个时候,虽然我们可以编辑待办事项,但是当我们编辑点击完成后,首页并没有展示出来,这是为什么,因为我们没有通知程序状态发生变化,所以程序没有重绘UI,所以要通过setState来告诉程序待办事项列表发生变化,需要更新:
//待办事项列表有新的变化,通过setState重绘页面UI
_todoItemsChanged(String text) {
setState(() {
_todoItems.add(text);
});
}
以上代码表示,当编辑完成后,会告诉程序待办事项清单已经发生变化,需要重绘UI。运行程序尝试点击按钮添加两条待办事项,如果一切正常的话会出现以下效果:
到这里工程已经完成大半了,下面我们要实现用户点击待办事项后,弹出对话框提示是否标记成完成,如果是则从列表中删除该事项。
因为是点击对应的事项,所以需要改造TodoItem():
//ListTile控件,描绘每个待办事项
Widget TodoItem(int i) {
return new ListTile(
title: new Text(_todoItems[i]),
onTap: () {
showDialog(
context: context,
builder: (context) {
return new AlertDialog(
title: new Text('"${_todoItems[i]}" 是否标记成已完成'),
actions: <Widget>[
new FlatButton(onPressed: null, child: new Text('否',style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent))),
new FlatButton(onPressed: null, child: new Text('是',style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent))),
],
);
});
},
);
}
以上代码主要更改了onTap内容,返回一个弹出对话框,并显示一条文本内容,同时包含两个按钮用于后续选择操作。
运行程序,先添加几条待办事项,然后点击其中一条会弹出对话框 如下图:
但是这个时候不管点击“是”还是“否”都没有效果,因为我们还没有添加点击逻辑,我们继续修改上面那段代码:
actions: <Widget>[
new FlatButton(
//点击否的话不进行任何操作,退出对话框
onPressed: () => Navigator.pop(context),
child: new Text(
'否',
style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent),
)),
new FlatButton(
//点击是,需要移除该事项,并告诉程序状态发生变化,需要重绘视图UI
onPressed: (){
//调用_removeTodoItem()方法刷新页面,,同时退出对话框
_removeTodoItem(i);
Navigator.pop(context);
},
child: new Text(
'是',
style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent),
)),
]
然后通过setState告诉程序事项删除,需要重绘视图UI:
//从列表中删除该事项,并告知程序状态发生变化,需要重绘视图UI
_removeTodoItem(int index){
setState(() {
_todoItems.removeAt(index);
});
}
这样,当点击某个事项后,如果点击否,不会有任何改变,当点击是,该事项会从当前展示区域消失。
那么现在基本功能就全部实现了,当然我们可以继续美化或者添加其他交互逻辑,比如在每个事项之间添加一条分割线,又比如在appbar上添加一个按钮,点击切换显示所有任务和已完成的任务等。后面再研究继续完善吧 _
下面附上全部代码:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Todo Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new TodoList(),
);
}
}
class TodoList extends StatefulWidget {
@override
State<StatefulWidget> createState() => new TodoListState();
}
class TodoListState extends State<TodoList> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('待办清单'),
),
body: new Center(
child: _todoItems.length == 0 ? hintText() : TodoListView()),
floatingActionButton: new FloatingActionButton(
onPressed: () => _todoEdit(),
tooltip: '新增待办事项',
child: new Icon(
Icons.add,
size: 25.0,
),
),
);
}
//当待办事项记录是0的时候,显示点击按钮开始添加提示语
Widget hintText() {
return new Text(
'点击按钮开始添加',
style: new TextStyle(fontSize: 20.0, color: Colors.grey),
);
}
//用于存放待办事项
List<String> _todoItems = [];
//ListView控件,用于滚动展示待办事项
Widget TodoListView() {
return new ListView.builder(itemBuilder: (context, index) {
if (index < _todoItems.length) {
return TodoItem(index);
}
});
}
//ListTile控件,描绘每个待办事项
Widget TodoItem(int i) {
return new ListTile(
title: new Text(_todoItems[i]),
onTap: () {
showDialog(
context: context,
builder: (context) {
return new AlertDialog(
title: new Text('"${_todoItems[i]}" 是否标记成已完成'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: new Text(
'否',
style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent),
)),
new FlatButton(
onPressed: (){
_removeTodoItem(i);
Navigator.pop(context);
},
child: new Text(
'是',
style: new TextStyle(
fontSize: 18.0, color: Colors.redAccent),
)),
],
);
});
},
);
}
//从列表中删除该事项,并告知程序状态发生变化,需要重绘视图UI
_removeTodoItem(int index){
setState(() {
_todoItems.removeAt(index);
});
}
//点击右下角的按钮后,将新的路由页面推入栈中,该页面包含一个文本编辑控件,用于用户编辑内容。
void _todoEdit() {
Navigator.push(context, new MaterialPageRoute(builder: (context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('添加待办事项'),
leading: new BackButton(),
),
body: new TextField(
decoration: new InputDecoration(
hintText: '编辑待办事项',
contentPadding: const EdgeInsets.all(10.0),
),
onSubmitted: (text) {
if (text.length == 0) {
Navigator.of(context).pop();
} else {
_todoItemsChanged(text);
Navigator.of(context).pop();
}
},
),
);
}));
}
//待办事项列表有新的变化,通过setState重绘页面UI
_todoItemsChanged(String text) {
setState(() {
_todoItems.add(text);
});
}
}