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
項目持續更新中.......

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容