Flutter 開發一個登錄頁面
業務邏輯
為瞭演示登錄跳轉,在分類瀏覽先做瞭一個簡單的按鈕,點擊跳轉到登錄頁面。實際的 App 中,通常會是觸發某些需要登錄才能查看的操作後再跳轉到登錄界面。
佈局分析
界面如上圖所示,從界面上看,整體內容區域是居中的,內容的佈局是一個簡單的列式佈局,包括瞭頂部的一個 Logo(通常是 App圖標),再往下是兩個文本輸入框,最後是登錄按鈕。整體佈局比較簡單,使用 Center 下嵌一個Column 進行列佈局即可。
圖片圓形裁剪
在 Flutter 中實行圖片圓形裁剪有兩個方式,一是使用外層的容器,通過將正方形的按圓形裁剪即可;二是使用內置的 CircleAvatar。不過從名字上看 CircleAvatar 用於頭像的,因此這裡使用容器的來實現圓形裁剪。封裝一個獲取圓形圖片的方法_getRoundImage,傳入圖片資源名稱和正方形邊長,代碼如下所示:
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 將邊框設置為圓形的邊框,半徑為邊長的一半,這樣就達到邊框是圓形的效果瞭。但是,需要額外設置一個屬性就是 clipBehavior,這是邊緣裁剪類型,默認是不裁剪的。這裡使用瞭 Clip.antiAlias(抗鋸齒)的方式進行裁剪,這種方式的裁剪效果最好,但是更耗資源,其他的裁剪方式如下:
- Clip.hardEdge:從名字就知道,這種方式很粗糙,但是裁剪的效率最快;
- Clip.antiAliasSaveLayer:最為精細的裁剪,但是非常慢,不建議使用;
- Clip.none:默認值,如果內容區沒有超出容器邊界的話,不會做任何裁剪。內容超出邊界的話需要使用別的裁剪方式防止內容溢出。
圓形扁平按鈕
這裡需要提一下, Flutter 2.0以前的扁平按鈕是FlatButton,使用起來很簡單,但是很多場合不太滿足,因此2.0以後引入瞭 TextButton 替代。TextButton 多瞭一個 style來裝飾按鈕樣式。具體可以看官方的文檔。這裡我們的按鈕需要設置背景色為主題色,然後按鈕文字顏色為白色,同時需要切成圓角,因此還是使用 Container 的邊界圓弧來實現。需要註意的是,默認按鈕的寬度是根據內容來的,因此為瞭讓按鈕撐滿屏幕,我們設置瞭 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()}'); }, ), ); }
按鈕點擊回調事件為 onPressed,這裡隻是簡單地打印瞭表單的內容。
TextField 文本框
TextField 是 Flutter 提供的文本輸入框,TextField 的屬性非常多,常用的屬性如下:
- keyboardType:鍵盤類型,可以指定是數字、字母、電話號碼、郵箱、日期等多種方式,通過與表單內容匹配的鍵盤類型可以提供輸入效率,進而改善用戶體驗。
- controller:TextEditingController 對象,TextEditingController 主要用於控制文本框的初始值,清除內容的操作。
- obscureText:是否需要隱藏輸入內容,如果為 true,則輸入內容會使用圓點顯示,通常用與密碼。
- decoration:文本框的裝飾,屬性也很多,可以指定前置圖標,邊框類型、後置組件等多種屬性,因此可以通過 decoration 獲得想要的文本框樣式。
- focusNode:聚焦點,可以通過這個來控制文本框是否獲取焦點,從而實現類似上一個下一個的輸入控制。
- onChanged:輸入值改變事件回調,通常用這個方法實現雙向綁定。
在這個案例中,我們使用瞭一個前置圖標用來表示輸入內容的類型,比如使用手機圖標代表輸入手機號,使用鎖代表代表密碼。同時使用瞭一個 Offstage作為後置的組件,用於在輸入內容後可以點擊清除內容。Offstage 組件是通過一個屬性offstage來控制組件是否顯示,這樣我們可以在沒有內容的時候隱藏它,有輸入內容的時候再顯示。
為瞭提高代碼復用性,使用瞭一個方法獲取通用的文本框,這裡主要是使用瞭 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: "輸入手機號", icon: Icon( Icons.mobile_friendly_rounded, size: 20.0, ), border: InputBorder.none, //使用 GestureDetector 實現手勢識別 suffixIcon: GestureDetector( child: Offstage( child: Icon(Icons.clear), offstage: _username == '', ), //點擊清除文本框內容 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; }); }, ); } //省略瞭上述列舉的代碼 }
頁面跳轉
在上層面的登錄按鈕上,我們增加瞭一個點擊事件,點擊後再跳到登錄頁,按鈕的響應代碼如下所示。這是頁面跳轉的最簡單的方式,使用 Navigator 導航器的 push方法實現頁面跳轉,後續會介紹如何通過路由實現頁面跳轉,那種方式更為優雅。
//... onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => LoginPage()), ); }, //...
總結
從代碼上看,功能雖然實現瞭,但是構建用戶名和密碼的代碼十分相似,有沒有辦法進一步提高代碼復用率,構建一個更為通用的表單組件呢?下篇我們將介紹如何來封裝。
以上就是Flutter 開發一個登錄頁面的詳細內容,更多關於Flutter 開發登錄頁面的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Flutter 如何封裝文本輸入框組件
- Flutter基本組件Basics Widget學習
- Flutter Component動畫的顯和隱最佳實踐
- Flutter驗證碼輸入框的2種方法實現
- Flutter LinearProgressIndicator使用指南分析