没错又是万年的demo作品- TodoList
今天我们先来做最最最阳春的TodoList,只先做简单的输入框及新增功能,其他功能我们之後再慢慢加上去。
从上图来看我们可以得知至少有两个大widget一个是输入框及todo卡片,首先我们可以先来实作todo卡片的样式。
以目前的todo卡片来说我们可以得知至少会有两个参数来表示序号及内容,为了方便跟页面的其他layout区隔当然是另外做成一个widget。
我们先新增一个档案todo_card.dart,然後在里面宣告一个 widget TodoCard
,那为什麽是使用 StatelessWidget
呢?依照目前的功能来说,我的内容应该会是从外部传入的所以我这个TodoCard
理论上是不需要有内部状态的。
class TodoCard extends StatelessWidget {
const TodoCard({required this.todoContent, required this.index, Key? key})
: super(key: key);
final String todoContent;
final int index;
@override
Widget build(BuildContext context) {
return Container();
}
}
首先宣告两个会从外面传进来的变数 todoContent
及 index
来表示待办事项的内容及序号。
宣告完 non-nullable的参数後记得要放进去 constructor里,而且因为是non-nullable 所以记得也要加上required
const TodoCard({required this.todoContent, required this.index, Key? key}) : super(key: key);
然後回到 main.dart 使用:
TodoCard(todoContent: '测试测试测试测试测试测试', index: 0)
TodoCard(todoContent: '测试测试测试测试测试测试测试测试测试测试测试测试', index: 1)
//...看你想放几个
好我们可以开始实作这张卡片的样式了,从图来看应该会有几个需求
所以我们先将原本的 Container
加上宽度、alignment及border
宽度的参数很好理解就是 width
,但为什麽没有border这个参数呢?跟昨天的颜色一样这些都会是要在 decoration
这个参数里设定。
child: Container(
width: 300,
alignment: Alignment.centerLeft
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(8),
),
),
那我们就用 BoxDecoration
这个class来实作我们 Container
的样式,这里会看到有 border
及
borderRadius
这两个参数分别就是控制border
本身的样式及这个Container
圆角弯曲程度。
Border.all()
就是直接对四边设定一样的参数,Border
的其他constructor 可以再查阅官方文件,那Border.all()
里面有几个可以调的参数 color
、 width
、 style
但我这边就先用预设样式就好,所以就不另外传入其他参数了。
alignment
就直接加上Alignment.centerLeft
表示靠左垂置中
最小高度会是在 constraints
这个参数设定,所以只要使用 BoxConstraints(minHeight: 48)
即可。
里面有padding就会是在 padding
里设置那就会是用 EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0)
所以目前会是长这样:
Container(
alignment: Alignment.centerLeft,
constraints: const BoxConstraints(minHeight: 48),
width: 300,
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(8),
),
),
那我们先将资料灌进去看看
Container(
alignment: Alignment.centerLeft,
constraints: const BoxConstraints(minHeight: 48),
width: 300,
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
child: Text('#$index: $todoContent'),
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(8),
),
),
好那剩下最後一个:每张卡片之间都有间距
其实也很简单就是用 Padding
包住这个 Container
就好。
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
alignment: Alignment.centerLeft,
constraints: const BoxConstraints(minHeight: 48),
width: 300,
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
child: Text('#$index: $todoContent'),
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(8),
),
),
);
}
这边就用 TextField
来实作,我们就直接这样使用:
SizedBox(
width: 300,
child: TextField(
decoration: const InputDecoration(labelText: '待办事项'),
),
),
相信这些参数不用再多加解释了。
那我们该如何利用 TextField
来达成这个功能呢?没错这时候就需要状态了。目前我们能知道todoList不只一个,所以我这边选择用 List
来实作。
在原本的放计数器状态的那边我们改成先宣告一个 List<String> _todoList
及接下来我们要来改变状态用的 _handleAddNewTodo
class _MyHomePageState extends State<MyHomePage>{
List<String> _todoList = ['123'];
void _handleAddNewTodo(String input) {
}
// 以下省略...
}
那接下来就是将这个state跟 TextField
串接上。
TextField
中有一个参数是 onSubmitted
所以我们就可以这样写,onSubmitted
是指我们在键盘按下enter後会执行的行为。
SizedBox(
width: 300,
child: TextField(
decoration: const InputDecoration(labelText: '待办事项'),
onSubmitted: (input) {
_handleAddNewTodo(input);
},
),
),
那接下来就是实作 _handleAddNewTodo
内部的行为
void _handleAddNewTodo(String input) {
setState(() {
_todoList = [..._todoList, input];
});
}
这里会看到这个操作: _todoList = [..._todoList, input];
...
是separate operator 意思,它可以将 List
摊平,大概会像是:
final a = [1,2,3]
final b = [4]
final c = [...a,...b] // [1,2,3,4]
所以回来看这个操作意思则是将原本的_todoList
摊平後加上新的input。
下一步我要将这个State与我们的 TodoCard
结合,
..._todoList.asMap().entries.map(
(entity) =>
TodoCard(todoContent: entity.value, index: entity.key),
),
这里有一个小重点:因为 List.map
不会有index这个值,所以这里是用 asMap
来达成在迭代时拥有index这件事。 List.asMap
会把List
变成Map
,而key则是原本的index。
至此我们就可以按enter新增卡片。
但还是有几个问题,如果我一直新增最後会发现超出边界导致画面有错误,以及我想要按下enter,TextField
里的东西清空该如何做?
就留到明天再为大家解答吧。
<<: [Day16] 团队管理:Role & Responsibility
>>: Swift纯Code之旅 Day21. 「ViewController好乱(3) - MVC下的Button动作」
当我们经营 WordPress 一段时间之後,许多朋友透过 Google 搜寻或是 FB 或其他社群...
经过连续十天的收集情报,体验了各式工具,可以发现前面介绍的大部分工具都是单纯的收集情报,少部分则可以...
rules of operator precedence 简单的小概念就是运算子(operator)...
上一篇我们完成了wireframe的绘制,这次我们要将草稿跟库拉皮卡一样,没有办法下船更具现化一点,...
我们之前介绍的 WordPress 原生区块有时会遇上不足够的情况,因为功能都偏向基础和简单。部分...