作一件很容易的小事並不難,困難的是把一件小事作到極致,這就十分不容易了!好比一個登錄頁,無非就一個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
第一步將上面的圖標修改一下,設置爲一個圓形圖形。通過詢問百度,得知使用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,
),
),
複製代碼
這裏須要解決上面提到的問題: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系統下鍵盤樣式:
- 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
還有兩個圖標對象,分別是prefixIcon
和suffixIcon
,通過編程合做夥伴有道翻譯的幫助,終於明白我在此處使用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,
)),
),
),
),
);
}
}
複製代碼
密碼明文顯示的開關與之相似,此處就不贅述了,後面會提供源碼。 接下來看看效果:
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行代碼,可是一邊查文檔一邊查資料,寫起來仍是各類酸爽!但願有大佬指出其中不合理的地方,畢竟我仍是菜鳥,但願能夠和大佬們一塊兒進步!