Flutter实现一个简单的待办事项

根据之前学的控件,我们来撸一个最简单的待办事项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组成。
尝试着运行一下,效果如下:


IMG_3412.PNG

下面我们添加一个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),
    );
  }

尝试运行后效果如下:


IMG_3413.PNG

你们肯定发现了,现在的事项是自动生成的,无法满足用户自己添加待办事项的需求,因此我们需要继续改造,让用户可以自己编辑添加事项。
我们通过点击右下角的添加按钮,跳转至添加待办事项页面,用户点击编辑框可以自己编辑内容,并保存结果。
我们添加一个编辑待办事项的页面:

//点击右下角的按钮后,将新的路由页面推入栈中,该页面包含一个文本编辑控件,用于用户编辑内容。
  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(),

再次尝试运行程序,当点击屏幕右下角按钮后,页面跳转至编辑待办事项的页面 如图:


IMG_3414.PNG

这个时候,虽然我们可以编辑待办事项,但是当我们编辑点击完成后,首页并没有展示出来,这是为什么,因为我们没有通知程序状态发生变化,所以程序没有重绘UI,所以要通过setState来告诉程序待办事项列表发生变化,需要更新:

//待办事项列表有新的变化,通过setState重绘页面UI
  _todoItemsChanged(String text) {
    setState(() {
      _todoItems.add(text);
    });
  }

以上代码表示,当编辑完成后,会告诉程序待办事项清单已经发生变化,需要重绘UI。运行程序尝试点击按钮添加两条待办事项,如果一切正常的话会出现以下效果:


IMG_3415.PNG

到这里工程已经完成大半了,下面我们要实现用户点击待办事项后,弹出对话框提示是否标记成完成,如果是则从列表中删除该事项。
因为是点击对应的事项,所以需要改造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内容,返回一个弹出对话框,并显示一条文本内容,同时包含两个按钮用于后续选择操作。
运行程序,先添加几条待办事项,然后点击其中一条会弹出对话框 如下图:


IMG_3417.PNG

但是这个时候不管点击“是”还是“否”都没有效果,因为我们还没有添加点击逻辑,我们继续修改上面那段代码:

              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);
    });
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,009评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,710评论 2 59
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,979评论 3 119
  • “有很多事,当我们离得很远的时候,我们看到的只是它的光明,当我们离得很近时,我们就会过多的注意到它阴暗的一面,看人...
    闲茶淡花阅读 540评论 0 4
  • 不知道你怎么看待中国这个看病难问题,如果你在国外就诊过,相必会有不一样的体验。最近业内网上流传一条信息,主题...
    金多多cjx阅读 387评论 0 0