flutter 項目 ToDo之登錄頁面

作一件很容易的小事並不難,困難的是把一件小事作到極致,這就十分不容易了!好比一個登錄頁,無非就一個logo、兩個輸入框以及幾個按鈕,以下圖:html

面臨的問題

爲何說困難呢?咱們一塊兒來看看細節:輸入類型、輸入長度、是否多行、自動校驗、異常提醒、輸入法的鍵盤動做按鈕圖標(即回車鍵位圖標)修改並實現點擊回調、提示文本、輸入框樣式設置、一鍵刪除等等,接下來咱們就來解決這些問題。前端

搭建佈局

import 'package:flutter/material.dart';

class Login extends StatefulWidget {
  @override
  _LoginState createState() => new _LoginState();
}

class _LoginState extends State<Login> {


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
          padding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 0.0),
          child: ListView(
            children: <Widget>[
              SizedBox(
                height: 72,
              ),
              Center(
                child: Image.asset(
                  "res/images/icon_logo.png",
                  scale: 1.5,
                ),
              ),
              SizedBox(
                height: 36,
              ),
              TextField(


              ),
              SizedBox(
                height: 14,
              ),
              TextField(

              ),
              SizedBox(
                height: 32,
              ),
              FlatButton(
                  child: RaisedButton(
                    padding: EdgeInsets.all(10),
                    child: Text(
                      '登陸/註冊',
                    ),
                  )),
            ],
          ),
        ));
  }
}
複製代碼

醜出天際了吧?我也這麼以爲,接下來咱們來一一解決上面提出的問題,實現效果圖!git

解決問題

1、圓形圖片

第一步將上面的圖標修改一下,設置爲一個圓形圖形。通過詢問百度,得知使用CircleAvatar能夠實現圓形圖片,說幹就幹:github

CircleAvatar(
            radius: 56.0,
            child: Image.asset(
              "res/images/icon_logo.png",
              scale: 1.8,
            ),
          )
複製代碼

可是後來使用過程當中發現個人圖片資源是背景是透明色,而全部的Image都會默認設置主題顏色爲背景色,仍是醜得哭兮流了!正則表達式

CircleAvatar的背景色修改成藍色,而後將圖片修改一下,增長一個白色的背景色,代碼以下:編程

CircleAvatar(
            backgroundColor: Colors.blue,
            radius: 56.0,
            child: Image.asset(
              "res/images/icon_logo.png",
              color: Colors.white,
              scale: 1.8,
            ),
          ),
複製代碼

看上去將個就了,接下來修改輸入框樣式。

2、設置輸入框樣式

這裏須要解決上面提到的問題:api

  • 輸入類型
  • 輸入長度
  • 是否多行
  • 提示文本
  • 密碼關閉明文展現
  • 下劃線自定義
  • 輸入法焦點換行

這些問題看似都很簡單,可是細節有魔鬼,看似簡單的地方每每埋藏着「炸彈」!接下來咱們就先來看看輸入框的Widget提供了哪些api供咱們調用?bash

const TextField({
  ...
    this.controller,
    this.focusNode,
    this.decoration = const InputDecoration(),
    TextInputType keyboardType,
    this.textInputAction,
    this.textCapitalization = TextCapitalization.none,
    this.style,
    this.strutStyle,
    this.textAlign = TextAlign.start,
    this.textDirection,
    this.autofocus = false,
    this.obscureText = false,
    this.autocorrect = true,
    this.maxLines = 1,
    this.minLines,
    this.expands = false,
    this.maxLength,
    this.maxLengthEnforced = true,
    this.onChanged,
    this.onEditingComplete,
    this.onSubmitted,
    this.inputFormatters,
    this.enabled,
    this.cursorWidth = 2.0,
    this.cursorRadius,
    this.cursorColor,
    this.keyboardAppearance,
    this.scrollPadding = const EdgeInsets.all(20.0),
    this.dragStartBehavior = DragStartBehavior.start,
    this.enableInteractiveSelection,
    this.onTap,
    this.buildCounter,
    this.scrollPhysics,
  ...
})
複製代碼
  • controller:編輯框的控制器,經過它能夠設置/獲取編輯框的內容、選擇編輯內容、監聽編輯文本改變事件。大多數狀況下咱們都須要顯式提供一個controller來與文本框交互。若是沒有提供controller,則TextField內部會自動建立一個。
  • focusNode:用於控制TextField是否佔有當前鍵盤的輸入焦點。它是咱們和鍵盤交互的一個handle。
  • InputDecoration:用於控制TextField的外觀顯示,如提示文本、背景顏色、邊框等。
  • keyboardType:用於設置該輸入框默認的鍵盤輸入類型,取值以下:
TextInputType枚舉值 含義
text 文本輸入鍵盤
multiline 多行文本,需和maxLines配合使用(設爲null或大於1)
number 數字;會彈出數字鍵盤
phone 優化後的電話號碼輸入鍵盤;會彈出數字鍵盤並顯示"* #"
datetime 優化後的日期輸入鍵盤;Android上會顯示「: -」
emailAddress 優化後的電子郵件地址;會顯示「@ .」
url 優化後的url輸入鍵盤; 會顯示「/ .」
  • textInputAction:鍵盤動做按鈕圖標(即回車鍵位圖標),它是一個枚舉值,有多個可選值,所有的取值列表讀者能夠查看API文檔,下面是當值爲TextInputAction.search時,原生Android系統下鍵盤樣式:

image-20180903181235471

  • style:正在編輯的文本樣式。
  • textAlign: 輸入框內編輯文本在水平方向的對齊方式。
  • autofocus: 是否自動獲取焦點。
  • obscureText:是否隱藏正在編輯的文本,如用於輸入密碼的場景等,文本內容會用「•」替換。
  • maxLines:輸入框的最大行數,默認爲1;若是爲null,則無行數限制。
  • maxLength和maxLengthEnforced :maxLength表明輸入框文本的最大長度,設置後輸入框右下角會顯示輸入的文本計數。maxLengthEnforced決定當輸入文本長度超過maxLength時是否阻止輸入,爲true時會阻止輸入,爲false時不會阻止輸入但輸入框會變紅。
  • onChange:輸入框內容改變時的回調函數;注:內容改變事件也能夠經過controller來監聽。
  • onEditingComplete和onSubmitted:這兩個回調都是在輸入框輸入完成時觸發,好比按了鍵盤的完成鍵(對號圖標)或搜索鍵(🔍圖標)。不一樣的是兩個回調簽名不一樣,onSubmitted回調是ValueChanged<String>類型,它接收當前輸入內容作爲參數,而onEditingComplete不接收參數。
  • inputFormatters:用於指定輸入格式;當用戶輸入內容改變時,會根據指定的格式來校驗。
  • enable:若是爲false,則輸入框會被禁用,禁用狀態不接收輸入和事件,同時顯示禁用態樣式(在其decoration中定義)。
  • cursorWidth、cursorRadius和cursorColor:這三個屬性是用於自定義輸入框光標寬度、圓角和顏色的。

瞭解了上面的api之後,咱們就不使用TextField,就是這麼任性!先不要想打人,由於咱們爲了自動檢測輸入的內容是否合法,咱們須要使用一個叫Form的東西來實現(固然controller也能夠,只是操做起來比較麻煩,像我這樣的懶人確定是不會這麼作的),並且二者的不少屬性都是同樣的,不信你看代碼:ide

Form(
            //設置globalKey,用於後面獲取FormState
            key: _formKey,
            //開啓自動校驗
            autovalidate: true,
            child: Column(
              children: <Widget>[
                TextFormField(
                autofocus: true,
                focusNode: _userFocusNode,
                keyboardType: TextInputType.emailAddress,
                textInputAction: TextInputAction.next,
                decoration: InputDecoration(
                  labelText: "用戶名",
                  hintText: "手機號或郵箱",
                  icon: Icon(Icons.person),
                ),
                // 校驗用戶名
                validator: (v) {
                  var userName = v.trim();
                  if (isChinaPhoneLegal(userName) ||
                      isEmailValid(userName)) {
                    return null;
                  } else {
                    return "用戶名必須是手機號或者郵箱地址";
                  }
                },
                onEditingComplete: () {
                  if (null == focusScopeNode) {
                    focusScopeNode = FocusScope.of(context);
                  }
                  focusScopeNode.requestFocus(_psdFocusNode);
                }),
                TextFormField(

                  textInputAction: TextInputAction.done,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    labelText: "密碼",
                    hintText: "您的登陸密碼",
                    icon: Icon(Icons.lock),
                  ),
                  obscureText: true,
                  //校驗密碼
                  validator: (v) {
                    // 可在此經過正則表達式校驗密碼是否符合規則
                    return v.trim().length > 5 ? null : "密碼不能少於6位";
                  },
                  onEditingComplete: () {
                    _userFocusNode.unfocus();
                    _psdFocusNode.unfocus();
                  },
                ),
              ],
            ),
          ),
複製代碼

目前基本實現了上面說的細節,可是左邊的圖標並無居中,這個確定逃不過UI的像素眼,因而仔細查看以後,發現InputDecoration還有兩個圖標對象,分別是prefixIconsuffixIcon,通過編程合做夥伴有道翻譯的幫助,終於明白我在此處使用prefixIcon就能夠圓滿解決問題了,此處就不單獨上圖了。可是還有兩個細節沒有完成,就是增長一鍵刪除和密碼明文顯示的控制按鈕,可是有了上面編程合做夥伴的幫助,彷佛問題也不難解決了!函數

首先,在Dart中一切可使用的變量引用都是對象(惋惜我仍是沒有對象),所以咱們能夠自定義一個對象繼承自suffixIcon,並實現它的點擊事件便可解決上面的需求。爲何須要自定義一個對象呢?由於若是在同一個類中,經過setState來刷新是整個類的全局刷新,會把全部的輸入框內容所有清除,這不是咱們指望看到的效果。以前看到更新Dialog的內容也是同一個思路,因此這裏咱們就定義了一個StatefulWidget來實現局部的刷新,代碼以下:

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

///自帶刪除的ITextField
typedef void ITextFieldCallBack(String content, bool isValid);

class UserNameField extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _UserNameFiledState();

  UserNameField(
      {@required this.fieldCallBack,
      this.focusNode,
      this.keyboardType,
      this.textInputAction,
      this.autofocus,
      this.maxLength,
      this.onEditingComplete,
      this.labelText,
      this.hintText,
      this.prefixIcon,
      this.suffixIcon,
      this.validator});

  final FocusNode focusNode;
  final TextInputType keyboardType;
  final TextInputAction textInputAction;
  final bool autofocus;
  final FormFieldValidator<String> validator;
  final ITextFieldCallBack fieldCallBack;
  final int maxLength;
  final VoidCallback onEditingComplete;
  final String labelText;
  final String hintText;
  final Widget prefixIcon;
  final Widget suffixIcon;
}

class _UserNameFiledState extends State<UserNameField> {
  bool _isShowCleanIcon = false;
  TextEditingController _controller = TextEditingController();
  GlobalKey _formKey = new GlobalKey<FormState>();

  @override
  void initState() {
    super.initState();
    _controller?.addListener(() {
      widget.fieldCallBack(
          _controller?.text, (_formKey.currentState as FormState).validate());
      bool state = _controller?.text?.length != 0;
      if (_isShowCleanIcon != state) {
        setState(() {
          _isShowCleanIcon = state;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      //開啓自動校驗
      autovalidate: true,
      key: _formKey,
      child: TextFormField(
        controller: _controller,
        focusNode: widget.focusNode,
        keyboardType: widget.keyboardType,
        textInputAction: widget.textInputAction,
        autofocus: widget.autofocus,
        maxLength: widget.maxLength,
        onEditingComplete: widget.onEditingComplete,
        validator: widget.validator,
        decoration: InputDecoration(
          labelText: widget.labelText,
          hintText: widget.hintText,
          prefixIcon: widget.prefixIcon,
          suffixIcon: GestureDetector(
            onTap: () {
              widget.fieldCallBack("", false);
              setState(() {
                _isShowCleanIcon = !_isShowCleanIcon;
              });
              _controller.clear();
            },
            child: _isShowCleanIcon
                ? widget.suffixIcon
                : IgnorePointer(
                    ignoring: true,
                    child: new Opacity(
                      opacity: 0.0,
                      child: widget.suffixIcon,
                    )),
          ),
        ),
      ),
    );
  }
}

複製代碼

密碼明文顯示的開關與之相似,此處就不贅述了,後面會提供源碼。 接下來看看效果:

3、修改Button的樣式

flutter對於按鈕提供了多種選擇(阿里拍賣前端團隊寫的Flutter開發者必備手冊 Flutter Go介紹了八種,官網介紹的是六種)

咱們的要求很簡單,就是一個圓角的長方形Button便可,這裏爲了有「水波動畫」和實質感的陰影,咱們選擇了RaisedButton來實現:

Padding(
            padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
            child: RaisedButton(
                color: Colors.blue,
                highlightColor: Colors.blue[700],
                colorBrightness: Brightness.dark,
                splashColor: Colors.grey,
                child: Text(
                  "登陸/註冊",
                  style: TextStyle(color: Colors.white, fontSize: 18.0),
                ),
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(20.0)),
                onPressed: () => {
                      if (_userNameValid && _psdValid)
                        {
                          // TODO 登陸
                        }
                    }),
          ),
複製代碼

最後再優化一下,增長忘記密碼和跳過登陸,此處不上代碼了,直接上圖:


說來一個簡單的登陸頁,寫了四個文件,其中兩個是自定義Widget、一個工具類,累計不到400行代碼,可是一邊查文檔一邊查資料,寫起來仍是各類酸爽!但願有大佬指出其中不合理的地方,畢竟我仍是菜鳥,但願能夠和大佬們一塊兒進步!

源碼

參考一:Flutter實戰:輸入框和表單

參考二:玩安卓 Flutter版本

參考三:flutter 自定義TextField,自帶刪除

相關文章
相關標籤/搜索