Flutter 入門指北之輸入處理(登錄界面實戰)

Flutter 入门指北之输入处理(登录界面实战)

碼個蛋(codeegg)第 589 次推文

前面提到基礎部件的時候,忘了提輸入內容處理部件,這裡補上,然後順帶擼個實際的界面吧

TextField

<code>const TextField({/<code><code> Key key,/<code><code> this.controller, // 定義一個 `TextEditingController` 實例,用來獲取輸入框內容等操作/<code><code> this.focusNode, // 定義一個 `FocusNode` 實例,判斷當前輸入框是否獲取到焦點等操作/<code><code> this.decoration = const InputDecoration, // 輸入框樣式,包括提醒字樣,hint 等等/<code><code> TextInputType keyboardType, // 輸入文本類型,例如 數字,email 等等/<code><code> this.textInputAction, // 鍵盤確認按鈕的事件類型/<code><code> this.textCapitalization = TextCapitalization.none,/<code><code> this.style, // 文字樣式/<code><code> this.textAlign = TextAlign.start, // 對齊方式/<code><code> this.textDirection, // 文字方向/<code><code> this.autofocus = false, // 是否自動獲取焦點/<code><code> this.obscureText = false, // 文字是否隱藏,多用於密碼/<code><code> this.autocorrect = true, /<code><code> this.maxLines = 1, ///<code><code> this.maxLength, // 最大長度/<code><code> this.maxLengthEnforced = true, // 設置最大長度後,輸入內容超出後是否強制不給輸入/<code><code> this.onChanged, // 輸入內容發生變化時候的回調/<code><code> this.onEditingComplete, // 輸入完畢的回調/<code><code> this.onSubmitted, // 提交內容的回調/<code><code> this.inputFormatters, // /<code><code> this.enabled, // 是否可輸入,false 不可輸入/<code><code> this.cursorWidth = 2.0, // 遊標寬度/<code><code> this.cursorRadius, // 遊標半徑/<code><code> this.cursorColor, // 遊標顏色/<code><code> this.keyboardAppearance, // 該屬性只在 iOS 設備有效/<code><code> this.scrollPadding = const EdgeInsets.all(20.0),/<code><code> this.enableInteractiveSelection,/<code><code> this.onTap, // 點擊事件/<code><code> })/<code>

那麼,簡單的來個輸入框示例吧,然後通過 Text 展示結果

<code>class HomePage extends StatefulWidget {/<code><code> @override/<code><code> _HomePageState createState => _HomePageState;/<code><code>}/<code>
<code>class _HomePageState extends State<homepage> {/<homepage>/<code><code> // 可以傳入初始值/<code><code> TextEditingController _editController = TextEditingController;/<code><code> FocusNode _editNode = FocusNode;/<code><code> // 保存按鈕點擊後的輸入內容值/<code><code> String _content = '';/<code><code> // 監聽輸入內容變化的內容值/<code><code> String _spyContent = '';/<code>
<code> @override/<code><code> void initState {/<code><code> super.initState;/<code><code> // 當輸入框獲取到焦點或者失去焦點的時候回調用/<code><code> _editNode.addListener( {/<code><code> print('edit has focus? => ${_editNode.hasFocus}');/<code><code> });/<code><code> }/<code>
<code> @override/<code><code> void dispose {/<code><code> // 記得銷燬,防止內存溢出/<code><code> _editController.dispose;/<code><code> _editNode.dispose;/<code><code> super.dispose;/<code><code> }/<code>
<code> @override/<code><code> Widget build(BuildContext context) {/<code><code> return Scaffold(/<code><code> appBar: AppBar(/<code><code> title: Text('Input Content'),/<code><code> ),/<code><code> body: Container(/<code><code> padding: const EdgeInsets.symmetric(horizontal: 12.0),/<code><code> child: Column(/<code><code> children: <widget>[/<widget>/<code><code> TextField(/<code><code> controller: _editController,/<code><code> focusNode: _editNode,/<code><code> decoration: InputDecoration(/<code><code> icon: Icon(Icons.phone_iphone, color: Theme.of(context).primaryColor),/<code><code> labelText: '請輸入手機號',/<code><code> helperText: '手機號',/<code><code> hintText: '手機號...在這兒輸入呢'),/<code><code> keyboardType: TextInputType.number,/<code><code> // 輸入類型為數字類型/<code><code> textInputAction: TextInputAction.done,/<code><code> style: TextStyle(color: Colors.redAccent, fontSize: 18.0),/<code><code> textDirection: TextDirection.ltr,/<code><code> maxLength: 11, // 最大長度為 11/<code><code> maxLengthEnforced: true, // 超過長度的不顯示/<code><code> onChanged: (v) { // 輸入的內容發生改變會調用/<code><code> setState(() => _spyContent = v);/<code><code> },/<code><code> onSubmitted: (s) { // 點擊確定按鈕時候會調用/<code><code> setState(() => _spyContent = _editController.value.text);/<code><code> },/<code><code> ),/<code><code> Padding(/<code><code> padding: const EdgeInsets.symmetric(vertical: 8.0),/<code><code> child: RaisedButton(/<code><code> onPressed: () { /<code><code> // 獲取輸入的內容/<code><code> setState(() => _content = _editController.value.text);/<code><code> // 清理輸入內容/<code><code> _editController.clear();/<code><code> setState(() => _spyContent = '');/<code><code> },/<code><code> child: Text('獲取輸入內容'))),/<code><code> // 展示輸入的內容,點擊按鈕會顯示/<code><code> Text(_content.isNotEmpty ? '獲取到輸入內容: $_content' : '還未獲取到任何內容...'),/<code><code> Padding(/<code><code> padding: const EdgeInsets.symmetric(vertical: 8.0),/<code><code> // 監聽輸入內容的變化,會跟隨輸入的內容進行改變/<code><code> child: Text('我是文字內容監聽:$_spyContent'),/<code><code> )/<code><code> ],/<code><code> )),/<code><code> );/<code><code> }/<code><code>}/<code>

這邊需要提下的是 setState 方法,該方法只有 StatefulWidget 才有,當需要修改某個值的內容的時候,通過該方法進行修改,最後的效果圖如下,當輸入框文字發生變化的時候,監聽的 Text 內容會隨之改變,獲取內容的 Text 當點擊按鈕了才發生變化

Flutter 入门指北之输入处理(登录界面实战)
Flutter 入门指北之输入处理(登录界面实战)

該部分代碼查看 text_field_main.dart 文件

那麼如果有個需求,在點擊按鈕的時候需要對輸入的內容的合理性進行檢測,當然可以通過 TextEditingController 的結果進行檢測,但是還有個更加方便的方法,可以直接使用部件 TextFormField 來實現,不過需要我們在外層加一個 Form 部件,接下來,就要準備通過 TextFormField 來擼一個登錄界面,但是這之前,前面有個坑需要先解決下

導入自定義的圖標

在這之前,涉及到 Icon 部件,都是使用的系統自帶的圖標,那麼如何導入第三方自定義圖標呢,馬上為你揭曉答案,首先我們需要打開「阿里媽媽」也就是 iconfont,不知道的小夥伴通過鏈接打開,然後需要註冊個賬戶,也可以直接通過 Github 等三方登錄,然後就可以搜索我們需要的圖標了,接下來需要擼一個登錄,那我們就找一個 用戶 和 密碼 的圖標吧,選擇喜歡的圖標,然後鼠標放到圖標會出現三個按鈕,直接點擊 購物車 那個按鈕,然後就可以通過頂部的 購物車 按鈕查看添加的圖標,點擊下載代碼,把資源文件下載到本地。

Flutter 入门指北之输入处理(登录界面实战)

解壓後,需要用到的文件有兩個,別的可以忽略

  1. demo_index.html 這邊用來查看圖標的 unicode

  2. iconfont.ttf 這邊就是圖標資源文件了


回到項目,創建一個文件夾 fonts ,和 images 同級,將 iconfont.ttf 文件放到該文件夾下,然後打開 pubspec.ymal 文件,註冊下導入的資源,可以自己命名 iconfont.ttf 文件名,便於自己發現就行,例如我命名為 third_part_icon.ttf,在註冊圖片下面繼續添加

<code>fonts:/<code><code> - family: ThirdPartIcons/<code><code> fonts:/<code><code> - asset: fonts/third_part_icon.ttf/<code>

註冊完了記得點擊 Package get,否則會找不到資源。接著新建個 third_icons.dart文件

<code>import 'package:flutter/material.dart';/<code>
<code>class ThirdIcons {/<code><code> // codePoint 值通過打開 `demo_index.html` 獲取/<code><code> // 會在相應 icon 下帶有相應的 code,把 `` 替換成 `0`,然後去掉最後的 `;` 即可/<code><code> // 例如  對應我們需要的圖標就是 0xe672/<code><code> static const IconData username = ThirdIconData(0xe672);/<code><code> static const IconData password = ThirdIconData(0xe62f);/<code><code>}/<code>
<code>class ThirdIconData extends IconData {/<code><code> // fontFamily 就是我們在 `pubspec.yaml` 中註冊的 family 值/<code><code> const ThirdIconData(int codePoint) : super(codePoint, fontFamily: 'ThirdPartIcons');/<code><code>}/<code>

接下來就可以通過該類導入需要的第三方圖標了。

導入第三方插件

其實 Flutter 中缺少很多功能,需要通過導入第三方插件來實現功能,插件就是 Flutter 和原生交互的橋樑,也就是說,要寫 Flutter 的插件,需要寫 Android 和 iOS 兩端代碼才可,否則只有在其中一個端能夠實現功能。好在有很多現成的插件已經開源,可以通過 FlutterPackage 搜索到,例如等會我們會需要用到 FlutterToast 這個插件,用來做提醒用,在 FlutterPackage 中搜索到插件後,打開項目中的 pubspec.ymal 文件,在 dependencies 類目下將 fluttertoast 插件引入,如圖:

Flutter 入门指北之输入处理(登录界面实战)

然後點擊 Package get 讓其導入即可,別的插件也是這樣導入。做好準備工作,我們就可以擼一個登錄界面了~

擼一個登錄界面

在開擼之前,我們先看下最終的效果圖吧,雖然是比較常用的界面

Flutter 入门指北之输入处理(登录界面实战)Flutter 入门指北之输入处理(登录界面实战)

因為兩個界面比較相似,所以這邊只貼外層的代碼和登錄的代碼,具體的代碼,可以查看源碼,已經推到 Github

<code>void main {/<code><code> runApp(LoginApp);/<code>
<code> if (Platform.isAndroid) {/<code><code> var style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);/<code><code> SystemChrome.setSystemUIOverlayStyle(style);/<code><code> }/<code><code>}/<code>
<code>/// 外層界面,包裹登錄界面和註冊界面,使用的都是前面講過的,忘記可以查看之前的章節/<code><code>class LoginApp extends StatelessWidget {/<code><code> @override/<code><code> Widget build(BuildContext context) {/<code><code> return MaterialApp(/<code><code> title: 'Login Demo',/<code><code> debugShowCheckedModeBanner: false,/<code><code> theme: ThemeData(primarySwatch: Colors.lightBlue),/<code><code> home: LoginHomePage,/<code><code> );/<code><code> }/<code><code>}/<code>
<code>class LoginHomePage extends StatefulWidget {/<code><code> @override/<code><code> _LoginHomePageState createState => _LoginHomePageState;/<code><code>}/<code>
<code>class _LoginHomePageState extends State<loginhomepage> with SingleTickerProviderStateMixin {/<loginhomepage>/<code><code> TabController _tabController;/<code><code> List<string> _pageIndicators = ['登錄', '註冊'];/<string>/<code><code> List<widget> _pages = ;/<widget>/<code><code> int _position = 0;/<code>
<code> @override/<code><code> void initState {/<code><code> super.initState;/<code><code> _tabController = TabController(length: _pageIndicators.length, vsync: this);/<code><code> // 將登錄界面和註冊界面添加到列表,用於放到 IndexStack 的 children 屬性/<code><code> _pages..add(LoginPage)..add(RegisterPage);/<code>
<code> _tabController.addListener( {/<code><code> // 當 tab 切換的時候,聯動 IndexStack 的 child 頁面也進行修改,通過 setState 來修改值/<code><code> if (_tabController.indexIsChanging) setState( => _position = _tabController.index);/<code><code> });/<code><code> }/<code>
<code> @override/<code><code> void dispose {/<code><code> super.dispose;/<code><code> }/<code>
<code> @override/<code><code> Widget build(BuildContext context) {/<code><code> // 先忽略.../<code><code> return Theme(/<code><code> data: ThemeData(primarySwatch: Colors.pink, iconTheme: IconThemeData(color: Colors.pink)),/<code><code> child: Scaffold(/<code><code> body: Container(/<code><code> padding: const EdgeInsets.all(20.0),/<code><code> alignment: Alignment.center,/<code><code> decoration:/<code><code> BoxDecoration(image: DecorationImage(image: AssetImage('images/login_bg.png'), fit: BoxFit.cover)),/<code><code> // 先忽略...下面會講,主要是解決軟鍵盤彈出的時候,界面內容會溢出的問題/<code><code> child: SingleChildScrollView(/<code><code> child: SafeArea(/<code><code> child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <widget>[/<widget>/<code><code> // 頂部頁面切換指示器,代碼可以參考 `app_bar_main.dart` 文件/<code><code> TabBar(/<code><code> indicatorSize: TabBarIndicatorSize.label,/<code><code> controller: _tabController,/<code><code> indicatorWeight: 4.0,/<code><code> indicatorColor: Colors.white,/<code><code> // 返回 tab 列表/<code><code> tabs: _pageIndicators/<code><code> .map((v) => Text(v, style: TextStyle(color: Colors.white, fontSize: 24.0)))/<code><code> .toList()),/<code><code> Padding(/<code><code> padding: const EdgeInsets.only(top: 30.0),/<code><code> child: SizedBox(/<code><code> // 切換界面列表/<code><code> child: IndexedStack(children: _pages, index: _position),/<code><code> // 指定高度/<code><code> height: MediaQuery.of(context).size.height / 2))/<code><code> ])),/<code><code> ),/<code><code> ),/<code><code> ));/<code><code> }/<code><code>}/<code>
<code>/// 登錄界面/<code><code>class LoginPage extends StatefulWidget {/<code><code> @override/<code><code> _LoginPageState createState => _LoginPageState;/<code><code>}/<code>
<code>class _LoginPageState extends State<loginpage> {/<loginpage>/<code><code> // 用於後面判斷表單內容是否有效/<code><code> GlobalKey<formstate> _formKey = GlobalKey;/<formstate>/<code><code> // 用於獲取輸入框的內容/<code><code> TextEditingController _usernameController = TextEditingController;/<code><code> TextEditingController _passwordController = TextEditingController;/<code>
<code> @override/<code><code> void initState {/<code><code> super.initState;/<code><code> }/<code>
<code> @override/<code><code> void dispose {/<code><code> // 防止內存溢出,記得銷燬..銷燬..銷燬/<code><code> _usernameController.dispose;/<code><code> _passwordController.dispose;/<code><code> super.dispose;/<code><code> }/<code>
<code> _login {/<code><code> // 取消焦點/<code><code> FocusScope.of(context).requestFocus(FocusNode);/<code>
<code> // 判斷表單是否有效/<code><code> if (_formKey.currentState.validate) {/<code><code> // 獲取輸入框內容/<code><code> var username = _usernameController.value.text;/<code><code> var password = _passwordController.value.text;/<code>
<code> // 判斷登錄條件/<code><code> if (username == 'kuky' && password == '123456')/<code><code> // 引入的三方插件方法,`Flutter` 沒有自帶的 `Taost`/<code><code> Fluttertoast.showToast(msg: '登錄成功');/<code><code> else/<code><code> Fluttertoast.showToast(msg: '登錄失敗');/<code><code> }/<code><code> }/<code>
<code> @override/<code><code> Widget build(BuildContext context) {/<code><code> return Form(/<code><code> // 將 key 設置給表單,用於判斷表單是否有效/<code><code> key: _formKey,/<code><code> child: Column(/<code><code> children: <widget>[/<widget>/<code><code> Padding(/<code><code> padding: const EdgeInsets.symmetric(vertical: 4.0),/<code><code> // 表單輸入框,參數同 TextField 基本類似/<code><code> child: TextFormField(/<code><code> controller: _usernameController,/<code><code> style: TextStyle(color: Colors.white, fontSize: 16.0),/<code><code> decoration: InputDecoration(/<code><code> icon: Icon(ThirdIcons.username, size: 24.0, color: Colors.white),/<code><code> labelText: '請輸入用戶名',/<code><code> labelStyle: TextStyle(color: Colors.white),/<code><code> helperStyle: TextStyle(color: Colors.white)),/<code><code> // 有效條件(為空不通過,返回提示語,通過返回 )/<code><code> validator: (value) => value.trim().isEmpty ? '用戶名不能為空' : ,/<code><code> ),/<code><code> ),/<code><code> Padding(/<code><code> padding: const EdgeInsets.symmetric(vertical: 4.0),/<code><code> child: TextFormField(/<code><code> obscureText: true,/<code><code> controller: _passwordController,/<code><code> style: TextStyle(color: Colors.white, fontSize: 16.0),/<code><code> decoration: InputDecoration(/<code><code> icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white),/<code><code> labelText: '請輸入密碼',/<code><code> labelStyle: TextStyle(color: Colors.white),/<code><code> helperStyle: TextStyle(color: Colors.white)),/<code><code> validator: (value) => value.trim().length < 6 ? '密碼長度不能小於6位' : ,/<code><code> ),/<code><code> ),/<code><code> Padding(/<code><code> padding: const EdgeInsets.only(top: 20.0),/<code><code> child: SizedBox(/<code><code> // 主要用於使 RaisedButton 和上層容器同寬/<code><code> width: MediaQuery.of(context).size.width,/<code><code> child: RaisedButton(/<code><code> color: Colors.pink,/<code><code> shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),/<code><code> onPressed: _login,/<code><code> child: Text(/<code><code> '登錄',/<code><code> style: TextStyle(color: Colors.white, fontSize: 20.0),/<code><code> )),/<code><code> ),/<code><code> )/<code><code> ],/<code><code> ));/<code><code> }/<code><code>}/<code>

擼完界面後,可以試下登錄效果,如果輸入框的內容,和 TextFormField 的 validator的條件不符合,則會顯示錯誤文字的提示

Flutter 入门指北之输入处理(登录界面实战)

如果按照條件用戶名為 kuky 密碼為 123456 (條件可以根據自己進行修改)則會顯示登錄成功的邏輯

以上代碼查看 login_home_page.dart 文件

註冊界面的邏輯和登錄界面的邏輯幾乎一樣,算是第一次實戰了,望小夥伴能夠好好的寫一遍

代碼地址:

https://github.com/kukyxs/flutter_arts_demos_app

Flutter 入门指北之输入处理(登录界面实战)Flutter 入门指北之输入处理(登录界面实战)


分享到:


相關文章: