Flutter入門指北(Part5)之輸入處理及實戰

NO IMAGE

該文已授權公眾號 「碼個蛋」,轉載請指明出處

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

TextField

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

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

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

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

Flutter入門指北(Part5)之輸入處理及實戰
Flutter入門指北(Part5)之輸入處理及實戰

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

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

導入自定義的圖標

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

Flutter入門指北(Part5)之輸入處理及實戰

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

  1. demo_index.html 這邊用來查看圖標的 unicode
  2. iconfont.ttf 這邊就是圖標資源文件了

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

fonts:
- family: ThirdPartIcons
fonts:
- asset: fonts/third_part_icon.ttf

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

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

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

導入第三方插件

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

Flutter入門指北(Part5)之輸入處理及實戰

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

擼一個登錄界面

在開擼之前,我們先看下最終的效果圖吧,雖然是比較常用的界面(因為圖都是自己搞的,比較醜請不要介意)

Flutter入門指北(Part5)之輸入處理及實戰
Flutter入門指北(Part5)之輸入處理及實戰

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

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

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

Flutter入門指北(Part5)之輸入處理及實戰

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

Flutter入門指北(Part5)之輸入處理及實戰

以上代碼查看 login_home_page.dart 文件

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

最後代碼的地址還是要的:

  1. 文章中涉及的代碼:demos

  2. 基於郭神 cool weather 接口的一個項目,實現 BLoC 模式,實現狀態管理:flutter_weather

  3. 一個課程(當時買了想看下代碼規範的,代碼更新會比較慢,雖然是跟著課上的一些寫代碼,但是還是做了自己的修改,很多地方看著不舒服,然後就改成自己的實現方式了):flutter_shop

如果對你有幫助的話,記得給個 Star,先謝過,你的認可就是支持我繼續寫下去的動力~

相關文章

Flutter入門指北(Part9)之彈窗和提示(SnackBar、BottomSheet、Dialog)

Flutter入門指北(Part8)之Sliver組件、NestedScrollView

Flutter入門指北(Part7)之滑動部件

Flutter入門指北(Part6)之路由