Flutter新人实战—从0开始开发一个DIY活动记录应用(四)用户交互之表单、图片获取

版权声明:本文为本人原创文章,未经本人允许不得转载。

上次我们完成了首页项目展示的基本工作,但是展示的内容都是我们预先写好的,今天我们就来说一说如何获取用户的输入来进行数据展示

废话不多说,我们正式开始。还记得首页底部有个添加符号的按钮吧,我们就是通过这个按钮来进入到新增项目信息的页面DiyAddDialog的。
下面我们开始绘制这个页面需要展示的UI,因为是获取用户输入,那么这个页面基本可以得知都是一些与用户进行交互的控件,比如日期选择器、文本输入、照片获取等等。
老规矩先看下基本效果:


IMG_0197.PNG

分析上图结构,我们可以看出一共需要用到以下几种控件:

1.日期选择器
2.文本输入控件
3.图片选择器

下面我们分别实现这些控件的显示,当然了这次实际写完的效果可能与上图有出入,因为女王大人给了新的要求,要增加一些内容,所以我会重新规划布局,但是只要你掌握了方法,其他都是一样的。

1.时间选择器

因为时间选择器比较独立,同时可能出现在不同的地方都会使用,所以我们单独写这个时间选择器控件。
在model文件夹下新建date_format.dart文件,在文件中编写日期选择器控件。
因为涉及到日期格式化,所以我们这里需要用到第三方插件包,在整个教程中这里是第一次涉及,所以我放一个中文官方文档链接,里面详细说明了如何使用第三方插件包:https://flutterchina.club/using-packages/#%E6%90%9C%E7%B4%A2packages
我们这里要用到的是国际化支持的 intl 包,代码如下:
date_format.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

class DatePicker extends StatelessWidget {
  DatePicker({Key key, this.selectedDate, this.selectDate}) : super(key: key);
  //已经选择的时间
  final DateTime selectedDate;
  //泛型是时间的改变回调函数,当选择时间改变后触发
  final ValueChanged<DateTime> selectDate;

  //选择时间方法
  _datePicker(BuildContext context) async {
    DateTime picked = await showDatePicker(
      context: context,
      initialDate: selectedDate,
      firstDate: DateTime(2015, 8),
      lastDate: DateTime(2050),
    );
    if (picked != null) {
      selectDate(picked);
    }
  }

  @override
  Widget build(BuildContext context) {
    return new ListTile(
      title: new InkWell(
        onTap: () => _datePicker(context),
        child: new Row(
          children: <Widget>[
            new Icon(Icons.today),
            new SizedBox(
              width: 20.0,
            ),
            new Text(DateFormat.yMd("en_US").format(selectedDate)),
          ],
        ),
      ),
    );
  }
}

以上代码中,showDatePicker是material材质的安卓日期选择器,返回的是一个将来的时间,所以这里需要用到异步操作。如果有不明白异步的同学可以去了解下简单的异步知识,我也只是了解基本的异步使用。
时间选择器我们写完了,要把时间选择器放到页面上,下一步我们在ui文件夹下继续新建diy_add_show.dart,用于存放新增项目页面的ui布局代码。
diy_add_show.dart

import 'package:activity_record/model/date_format.dart';
import 'package:flutter/material.dart';

class DiyAddShow extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new DiyAddShowState();
}

class DiyAddShowState extends State<DiyAddShow> {
  //实例化对象已选择的时间,并赋予初始值是当前时间
  DateTime _selectedDate = DateTime.now();
  @override
  Widget build(BuildContext context) {
    //safeArea是安全区域小控件,通过足够的填充来保护其子控件,以避免显示内容被系统级元素覆盖或出现异常。
    return new SafeArea(
      top: false,
      bottom: false,
      child: new ListView(
        children: <Widget>[
          new DatePicker(
            selectedDate: _selectedDate,
            selectDate: (DateTime date) {
              setState(() {
                _selectedDate = date;
              });
            },
          )
        ],
      ),
    );
  }
}

然后我们回到DiyAddDialog.dart,文件,修改body部分内容,把新增项目的ui放进去:
diy_add_dialog.dart

import 'package:activity_record/ui/diy_add_show.dart';
import 'package:flutter/material.dart';

/*
diy活动新增页面
涉及用户输入所以继承自状态可变的StatefulWidget
采用全屏对话框的形式展现
 */
class DiyAddDialog extends StatefulWidget {
  @override
  DiyAddDialogState createState() => new DiyAddDialogState();
}

class DiyAddDialogState extends State<DiyAddDialog> {
  final _title = '新增活动';
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(_title),
      ),
      body: new DiyAddShow(),//新增项目的UI
    );
  }
}

终于到看成果的时候了,程序跑起来,点击首页底部的添加按钮,看下效果:


image.png

顶部出现了我们的日期显示,默认是当前时间,当然显示格式大家可以根据需求进行调整。
当点击日期后,会弹出时间选择器就可以修改时间了:


image.png
以上这个是安卓材质的时间选择器,flutter仓库包里还有诸如ios风格的时间选择器,比如 flutter_cupertino_date_picker,大家可以尝试着把上面的时间选择器换成ios风格的。

2.文本输入框

文本输入框的控件相对其实比较简单,主要就是TextField控件来实现用户输入,而TextField里有个获取用户输入信息的控制器,添加他们
在diy_add_show.dart文件里添加各个输入框的控制器用于获取用户输入的值:
diy_add_show.dart

//活动名称输入框控制器
  TextEditingController _nameTextEditingController =
      new TextEditingController();
  //活动地点输入框控制器
  TextEditingController _placeTextEditingController =
      new TextEditingController();
  //活动联系人输入框控制器
  TextEditingController _contactTextEditingController =
      new TextEditingController();
  //活动单价输入框控制器
  TextEditingController _singlePriceTextEditingController =
      new TextEditingController();
  //活动份数输入框控制器
  TextEditingController _numsTextEditingController =
      new TextEditingController();
  //活动物料成本输入框控制器
  TextEditingController _itemCostTextEditingController =
      new TextEditingController();
  //活动人员成本输入框控制器
  TextEditingController _laborCostTextEditingController =
      new TextEditingController();

然后我们先添加活动基本信息的输入框:

//活动文字信息输入
  Widget _infoTextField(
      IconData icon, TextEditingController controller, String hint) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(18.0, 10.0, 18.0, 0.0),
      child: new TextField(
        autofocus: true,
        controller: controller,
        //这个文本框的装饰包含了一个图标和提示文字
        decoration: new InputDecoration(icon: new Icon(icon), hintText: hint),
      ),
    );
  }

然后添加金额数字输入框:

 //活动价格份数信息输入框封装
  Widget _amountTextField(TextEditingController controller, String labelText,
      String prefixText, String suffixText) {
    return new TextField(
      //键盘类型适用于登录的
      keyboardType: TextInputType.numberWithOptions(signed: true),
      controller: controller,
      /*
      输入框装饰:
      包含边框、标题、提示文本、后缀文本
      */
      decoration: new InputDecoration(
          border: OutlineInputBorder(),
          labelText: labelText,
          prefixText: prefixText,
          suffixText: suffixText,
          suffixStyle: new TextStyle(color: Colors.green)),
    );
  }

不同的输入框我们已经封装写完,最后就是根据自己的设计习惯将他们摆放在页面上展示出来,我们继续在DiyAddShow.dart中日期选择器的下面把输入框组合起来,完成最后的展示:

              _infoTextField(Icons.spa, _nameTextEditingController, '活动名称'),
              _infoTextField(
                  Icons.my_location, _placeTextEditingController, '活动地点'),
              _infoTextField(
                  Icons.tag_faces, _contactTextEditingController, '联系人'),
              //把两个金额输入框放在一个包含padding的横向布局里
              new Padding(
                padding: const EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0.0),
                child: new Row(
                  children: <Widget>[
                    new Expanded(
                      child: _amountTextField(_singlePriceTextEditingController,
                          '活动单价', '\¥', 'CNY'),
                    ),
                    new SizedBox(
                      width: 10.0,
                    ),
                    new Expanded(
                      child: _amountTextField(
                          _numsTextEditingController, '活动份数', '\@', '份'),
                    ),
                  ],
                ),
              ),
              //把两个金额输入框放在一个包含padding的横向布局里
              new Padding(
                padding: const EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0.0),
                child: new Row(
                  children: <Widget>[
                    new Expanded(
                      child: _amountTextField(
                          _itemCostTextEditingController, '物料成本', '\¥', 'CNY'),
                    ),
                    new SizedBox(
                      width: 10.0,
                    ),
                    new Expanded(
                      child: _amountTextField(
                          _laborCostTextEditingController, '人员成本', '\¥', 'CNY'),
                    ),
                  ],
                ),
              ),

到这里基本新增项目的页面UI就基本实现了,赶紧跑起来看看效果吧:


image.png

image.png

3.图片选择器

flutter支持不同方式显示图片,比如网络、本地、缓存等等,但是默认好像是没有从相册选择图片的,所以这里就需要用到第三方插件包:image_picker: ^0.4.10,使用方法在上面贴过教程链接,这里就不再介绍如何使用。
diy_add_show.dart文件中添加如下代码:

  String _imagePath;
//从本地相册选择图片
  Future _getImagePath() async {
    File file = await ImagePicker.pickImage(source: ImageSource.gallery);
    if (file != null) {
      setState(() {
        _imagePath = file.path;
      });
    }
  }

以上代码就是通过图像选择器插件从相册选取,并获得图片的本地路径。以后数据库保存的时候不是直接保存图片,而是保存图片的地址,所以这里我们预先获得图片路径。其中ImageSource.gallery改成ImageSource.camera就可以调用摄像头拍照了。
我们继续在金额输入框下面添加展示照片的控件,代码如下:

            new Padding(
                padding: const EdgeInsets.all(18.0),
                child: new SizedBox(
                    height: 120.0,
                    child: new Container(
                      decoration: new BoxDecoration(
                          border: new Border.all(color: Colors.grey),
                          borderRadius: new BorderRadius.circular(5.0)),
                      child: new InkWell(
                        onTap: () => _getImagePath(),
                        child: _image == null
                            ? new Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  new Icon(Icons.photo,size: 40.0,color: Theme.of(context).primaryColor,),
                                  new Text('从相册选取图片')
                                ],
                              )
                            //Image.file是根据路径获得图片
                            : new Image.file(
                                File(_imagePath),
                                fit: BoxFit.cover,
                              ),
                      ),
                    )),
              ),

以上代码显示一个120高度的容器,如果图片还未选择,那么显示一个图标和选取图片的文字提示,如果选取了那么直接显示图片。
运行程序看下效果,没有选择图片之前:


image.png

点击从相册选取后:


image.png

本文总结

通过本文,我想你应该掌握了日期选择器、文本输入框、图片选择器的使用,并对上篇文章介绍的控件之间组合布局有了更深的了解,使用起来肯定更加熟练了。
下次我们将开始介绍页面之间的数据传送。

最后附上项目源码地址:https://gitee.com/xusujun33/activity_record_jia.git
项目持续更新中.......

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

推荐阅读更多精彩内容