TextField
可實現驗證碼輸入,包括顯示 數字鍵盤
,與一次只能輸入 一個數字
。本範例使用 FocusTraversalGroup
。
Version
Flutter 3.24.5
Flutter
- Android 與 iOS 都成功使用
TextField
實現輸入驗證碼
TextField
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Home extends StatefulWidget {
const Home({super.key});
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final List<String> _codes = List.generate(
6,
(_) => '',
);
final FocusNode _firstFocusNode = FocusNode();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) => _firstFocusNode.requestFocus(),
);
}
Widget build(BuildContext context) {
var appBar = AppBar(
title: const Text(
'Verification Code',
),
);
var codes = FocusTraversalGroup(
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
6,
(index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 4,
),
width: 40,
child: TextField(
keyboardType: TextInputType.number,
maxLength: 1,
textAlign: TextAlign.center,
focusNode: index == 0 ? _firstFocusNode : null,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
counterText: '',
border: OutlineInputBorder(),
),
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
FocusScope.of(context).nextFocus();
} else {
FocusScope.of(context).unfocus();
}
},
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
),
);
},
),
),
);
var body = Center(
child: codes,
);
return Scaffold(
appBar: appBar,
body: body,
);
}
void dispose() {
_firstFocusNode.dispose();
super.dispose();
}
}
Line 12
final List<String> _codes = List.generate(
6,
(_) => '',
);
_codes
:以 array 儲存所輸入的驗證碼,初始值皆為 empty string(_) => ''
:List.generate()
的 callback 的型別原本為(int index) => {}
,因為index
沒用到,所以必須以_
代替
Line 16
final FocusNode _firstFocusNode = FocusNode();
- 定義第一個 TextField 所需的 FocusNode
Line 18
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) => _firstFocusNode.requestFocus(),
);
}
WidgetsBinding.instance.addPostFrameCallback()
:在 Widget Tree render 完時觸發,對第一個TextField
進行 focus
Line 34
var codes = FocusTraversalGroup(
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
6,
(index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 4,
),
width: 40,
child: TextField(
keyboardType: TextInputType.number,
maxLength: 1,
textAlign: TextAlign.center,
focusNode: index == 0 ? _firstFocusNode : null,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
counterText: '',
border: OutlineInputBorder(),
),
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
FocusScope.of(context).nextFocus();
} else {
FocusScope.of(context).unfocus();
}
},
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
),
);
},
),
),
);
FocusTraversalGroup
:將驗證碼輸入的多個 TextField 定為一個 group,如此 focus 將可依序跳轉keyboardType: TextInputType.number
:自動顯示數字鍵盤
maxLength: 1
:一次只能輸入一個字,適合輸入驗證碼textAlign: TextAlign.center
:輸入的驗證碼水平置中focusNode: index == 0 ? _firstFocusNode : null
:第一個 TextField 將取得 focusinputFormatters
:設定TextField
能輸入的限制FilteringTextInputFormatter.digitsOnly
:只能輸入數字
decoration
counterText: ''
:當搭配maxLength
時,counterText
會顯示當前字數 / 最大字數
的統計,但用於驗證碼輸入時則不需要,因此設定為 empty stringborder: OutlineInputBorder()
:驗證輸入為圓框
Line 57
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
FocusScope.of(context).nextFocus();
} else {
FocusScope.of(context).unfocus();
}
},
- 當輸入時,會將值寫入
_codes
array - 當不是
最後一個
驗證碼時,會自動跳到下一個驗證碼 - 當輸入完最一個驗證碼時,自動隱藏
數字鍵盤
會在
輸入後
才取得index
,而不是輸入前
取得index
Line 70
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
- 離開
TextField
自動隱藏數字鍵盤
Conclusion
FocusTraversalGroup
讓驗證碼輸入跳轉 focus 更加容易,只需簡單nextFocus()
即可initState()
內配合WidgetsBinding.instance.addPostFrameCallback()
可視為 Vue 的mounted()
,專門用來對 Widget Tree render 完要執行的初始化操作,如設置焦點
、請求數據
或啟動畫面動畫