Flutter 開(kāi)發(fā)一個(gè)登錄頁(yè)面
業(yè)務(wù)邏輯
為了演示登錄跳轉(zhuǎn),在分類(lèi)瀏覽先做了一個(gè)簡(jiǎn)單的按鈕,點(diǎn)擊跳轉(zhuǎn)到登錄頁(yè)面。實(shí)際的 App 中,通常會(huì)是觸發(fā)某些需要登錄才能查看的操作后再跳轉(zhuǎn)到登錄界面。
布局分析

界面如上圖所示,從界面上看,整體內(nèi)容區(qū)域是居中的,內(nèi)容的布局是一個(gè)簡(jiǎn)單的列式布局,包括了頂部的一個(gè) Logo(通常是 App圖標(biāo)),再往下是兩個(gè)文本輸入框,最后是登錄按鈕。整體布局比較簡(jiǎn)單,使用 Center 下嵌一個(gè)Column 進(jìn)行列布局即可。
圖片圓形裁剪
在 Flutter 中實(shí)行圖片圓形裁剪有兩個(gè)方式,一是使用外層的容器,通過(guò)將正方形的按圓形裁剪即可;二是使用內(nèi)置的 CircleAvatar。不過(guò)從名字上看 CircleAvatar 用于頭像的,因此這里使用容器的來(lái)實(shí)現(xiàn)圓形裁剪。封裝一個(gè)獲取圓形圖片的方法_getRoundImage,傳入圖片資源名稱(chēng)和正方形邊長(zhǎng),代碼如下所示:
Widget _getRoundImage(String imageName, double size) {
return Container(
width: size,
height: size,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(size / 2)),
),
child: Image.asset(
imageName,
fit: BoxFit.fitWidth,
),
);
}
這里使用了 BoxDecoration 將邊框設(shè)置為圓形的邊框,半徑為邊長(zhǎng)的一半,這樣就達(dá)到邊框是圓形的效果了。但是,需要額外設(shè)置一個(gè)屬性就是 clipBehavior,這是邊緣裁剪類(lèi)型,默認(rèn)是不裁剪的。這里使用了 Clip.antiAlias(抗鋸齒)的方式進(jìn)行裁剪,這種方式的裁剪效果最好,但是更耗資源,其他的裁剪方式如下:
- Clip.hardEdge:從名字就知道,這種方式很粗糙,但是裁剪的效率最快;
- Clip.antiAliasSaveLayer:最為精細(xì)的裁剪,但是非常慢,不建議使用;
- Clip.none:默認(rèn)值,如果內(nèi)容區(qū)沒(méi)有超出容器邊界的話,不會(huì)做任何裁剪。內(nèi)容超出邊界的話需要使用別的裁剪方式防止內(nèi)容溢出。
圓形扁平按鈕
這里需要提一下, Flutter 2.0以前的扁平按鈕是FlatButton,使用起來(lái)很簡(jiǎn)單,但是很多場(chǎng)合不太滿(mǎn)足,因此2.0以后引入了 TextButton 替代。TextButton 多了一個(gè) style來(lái)裝飾按鈕樣式。具體可以看官方的文檔。這里我們的按鈕需要設(shè)置背景色為主題色,然后按鈕文字顏色為白色,同時(shí)需要切成圓角,因此還是使用 Container 的邊界圓弧來(lái)實(shí)現(xiàn)。需要注意的是,默認(rèn)按鈕的寬度是根據(jù)內(nèi)容來(lái)的,因此為了讓按鈕撐滿(mǎn)屏幕,我們?cè)O(shè)置了 Container 的寬度為 double.infinity。代碼如下所示:
Widget _getLoginButton() {
return Container(
height: 50,
width: double.infinity,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(4.0),
),
child: TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
backgroundColor:
MaterialStateProperty.all<Color>(Theme.of(context).primaryColor),
),
child: Text(
'登錄',
),
onPressed: () {
print(
'Login: username=${_username.trim()}, password=${_password.trim()}');
},
),
);
}
按鈕點(diǎn)擊回調(diào)事件為 onPressed,這里只是簡(jiǎn)單地打印了表單的內(nèi)容。
TextField 文本框
TextField 是 Flutter 提供的文本輸入框,TextField 的屬性非常多,常用的屬性如下:
- keyboardType:鍵盤(pán)類(lèi)型,可以指定是數(shù)字、字母、電話號(hào)碼、郵箱、日期等多種方式,通過(guò)與表單內(nèi)容匹配的鍵盤(pán)類(lèi)型可以提供輸入效率,進(jìn)而改善用戶(hù)體驗(yàn)。
- controller:TextEditingController 對(duì)象,TextEditingController 主要用于控制文本框的初始值,清除內(nèi)容的操作。
- obscureText:是否需要隱藏輸入內(nèi)容,如果為 true,則輸入內(nèi)容會(huì)使用圓點(diǎn)顯示,通常用與密碼。
- decoration:文本框的裝飾,屬性也很多,可以指定前置圖標(biāo),邊框類(lèi)型、后置組件等多種屬性,因此可以通過(guò) decoration 獲得想要的文本框樣式。
- focusNode:聚焦點(diǎn),可以通過(guò)這個(gè)來(lái)控制文本框是否獲取焦點(diǎn),從而實(shí)現(xiàn)類(lèi)似上一個(gè)下一個(gè)的輸入控制。
- onChanged:輸入值改變事件回調(diào),通常用這個(gè)方法實(shí)現(xiàn)雙向綁定。
在這個(gè)案例中,我們使用了一個(gè)前置圖標(biāo)用來(lái)表示輸入內(nèi)容的類(lèi)型,比如使用手機(jī)圖標(biāo)代表輸入手機(jī)號(hào),使用鎖代表代表密碼。同時(shí)使用了一個(gè) Offstage作為后置的組件,用于在輸入內(nèi)容后可以點(diǎn)擊清除內(nèi)容。Offstage 組件是通過(guò)一個(gè)屬性offstage來(lái)控制組件是否顯示,這樣我們可以在沒(méi)有內(nèi)容的時(shí)候隱藏它,有輸入內(nèi)容的時(shí)候再顯示。
為了提高代碼復(fù)用性,使用了一個(gè)方法獲取通用的文本框,這里主要是使用了 Container包裹以控制邊距和文本框下的分隔線:
Widget _getInputTextField(
TextInputType keyboardType, {
FocusNode focusNode,
controller: TextEditingController,
onChanged: Function,
InputDecoration decoration,
bool obscureText = false,
height = 50.0,
}) {
return Container(
height: height,
margin: EdgeInsets.all(10.0),
child: Column(
children: [
TextField(
keyboardType: keyboardType,
focusNode: focusNode,
obscureText: obscureText,
controller: controller,
decoration: decoration,
onChanged: onChanged,
),
Divider(
height: 1.0,
color: Colors.grey[400],
),
],
),
);
}
完整代碼
class _LoginPageState extends State<LoginPage> {
//TextEditingController可以使用 text 屬性指定初始值
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
String _username = '', _password = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('登錄'),
brightness: Brightness.dark,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_getRoundImage('images/logo.png', 100.0),
SizedBox(
height: 60,
),
_getUsernameInput(),
_getPasswordInput(),
SizedBox(
height: 10,
),
_getLoginButton(),
],
),
),
);
}
Widget _getUsernameInput() {
return _getInputTextField(
TextInputType.number,
controller: _usernameController,
decoration: InputDecoration(
hintText: "輸入手機(jī)號(hào)",
icon: Icon(
Icons.mobile_friendly_rounded,
size: 20.0,
),
border: InputBorder.none,
//使用 GestureDetector 實(shí)現(xiàn)手勢(shì)識(shí)別
suffixIcon: GestureDetector(
child: Offstage(
child: Icon(Icons.clear),
offstage: _username == '',
),
//點(diǎn)擊清除文本框內(nèi)容
onTap: () {
this.setState(() {
_username = '';
_usernameController.clear();
});
},
),
),
//使用 onChanged 完成雙向綁定
onChanged: (value) {
this.setState(() {
_username = value;
});
},
);
}
Widget _getPasswordInput() {
return _getInputTextField(
TextInputType.text,
obscureText: true,
controller: _passwordController,
decoration: InputDecoration(
hintText: "輸入密碼",
icon: Icon(
Icons.lock_open,
size: 20.0,
),
suffixIcon: GestureDetector(
child: Offstage(
child: Icon(Icons.clear),
offstage: _password == '',
),
onTap: () {
this.setState(() {
_password = '';
_passwordController.clear();
});
},
),
border: InputBorder.none,
),
onChanged: (value) {
this.setState(() {
_password = value;
});
},
);
}
//省略了上述列舉的代碼
}
頁(yè)面跳轉(zhuǎn)
在上層面的登錄按鈕上,我們?cè)黾恿艘粋€(gè)點(diǎn)擊事件,點(diǎn)擊后再跳到登錄頁(yè),按鈕的響應(yīng)代碼如下所示。這是頁(yè)面跳轉(zhuǎn)的最簡(jiǎn)單的方式,使用 Navigator 導(dǎo)航器的 push方法實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),后續(xù)會(huì)介紹如何通過(guò)路由實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),那種方式更為優(yōu)雅。
//...
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LoginPage()),
);
},
//...
總結(jié)
從代碼上看,功能雖然實(shí)現(xiàn)了,但是構(gòu)建用戶(hù)名和密碼的代碼十分相似,有沒(méi)有辦法進(jìn)一步提高代碼復(fù)用率,構(gòu)建一個(gè)更為通用的表單組件呢?下篇我們將介紹如何來(lái)封裝。
以上就是Flutter 開(kāi)發(fā)一個(gè)登錄頁(yè)面的詳細(xì)內(nèi)容,更多關(guān)于Flutter 開(kāi)發(fā)登錄頁(yè)面的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android手機(jī)(設(shè)備)連接掃描槍掃碼遇到的問(wèn)題
這篇文章給大家分享了Android手機(jī)(設(shè)備)連接掃描槍掃碼遇到的問(wèn)題以及解決辦法,有需要的參考下。2018-07-07
淺談Android中適配器的notifyDataSetChanged()為何有時(shí)不刷新
這篇文章主要介紹了淺談Android中適配器的notifyDataSetChanged()為何有時(shí)不刷新,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Android RecyclerView使用GridLayoutManager間距設(shè)置的方法
本篇文章主要介紹了Android RecyclerView使用GridLayoutManager間距設(shè)置的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android第三方文件選擇器aFileChooser使用方法詳解
這篇文章主要介紹了Android第三方文件選擇器aFileChooser的使用方法詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android 給應(yīng)用程序的icon添加未讀消息個(gè)數(shù)提示(紅圈內(nèi)數(shù)字)
本文主要介紹了Android 給應(yīng)用程序的icon添加未讀消息個(gè)數(shù)提示(紅圈內(nèi)數(shù)字)的方法。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Android編程錄音工具類(lèi)RecorderUtil定義與用法示例
這篇文章主要介紹了Android編程錄音工具類(lèi)RecorderUtil定義與用法,結(jié)合實(shí)例形式分析了Android錄音工具類(lèi)實(shí)現(xiàn)開(kāi)始錄音、停止錄音、取消錄音、獲取錄音信息等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
Android4.2中全屏或者取消標(biāo)題欄的方法總結(jié)
有的時(shí)候我們會(huì)看到,會(huì)先出現(xiàn)標(biāo)題欄,然后再消失,因?yàn)槲覀冎皇窃赼ctivity的oncreate方法中定義的,其他實(shí)現(xiàn)方法如下,感興趣的朋友可以了解下哈2013-06-06
Android Canvas drawText文字居中的一些事(圖解)
這篇文章主要給大家介紹了關(guān)于Android Canvas drawText文字居中的一些事,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12

