TextField
可實現驗證碼輸入,包括顯示 數字鍵盤
,與一次只能輸入 一個數字
。本範例使用 FocusNode
。
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() => _Home();
}
class _Home extends State<Home> {
final List<String> _codes = List.generate(
6,
(_) => '',
);
final List<FocusNode> _focusNodes = List.generate(
6,
(_) => FocusNode(),
);
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) => _focusNodes[0].requestFocus(),
);
}
Widget build(BuildContext context) {
var appBar = AppBar(
title: const Text('Verification Code'),
);
var codes = List.generate(
6,
(index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 4,
),
width: 40,
child: TextField(
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
maxLength: 1,
textAlign: TextAlign.center,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
counterText: '',
border: OutlineInputBorder(),
),
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
_focusNodes[index + 1].requestFocus();
} else {
_focusNodes[index].unfocus();
}
},
onTapOutside: (event) {
_focusNodes[index].unfocus();
},
),
);
},
);
var body = Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: codes,
),
);
return Scaffold(
appBar: appBar,
body: body,
);
}
void dispose() {
for (var focusNode in _focusNodes) {
focusNode.dispose();
}
super.dispose();
}
}
Line 12
final List<String> _verificationCode = List.generate(
6,
(_) => '',
);
_codes
:以 array 儲存所輸入的驗證碼,初始值皆為 empty string(_) => ''
:List.generate()
的 callback 的型別原本為(int index) => {}
,因為index
沒用到,所以必須以_
代替
Line 16
final List<FocusNode> _focusNodes = List.generate(
6,
(_) => FocusNode(),
);
_focusNodes
:建立每個TextField
的FocusNode
Line 21
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) => _focusNodes[0].requestFocus(),
);
}
WidgetsBinding.instance.addPostFrameCallback()
:在 Widget Tree render 完時觸發,對第一個TextField
進行 focus
Line 35
var codes = List.generate(
6,
(index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 4,
),
width: 40,
child: TextField(
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
maxLength: 1,
textAlign: TextAlign.center,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
counterText: '',
border: OutlineInputBorder(),
),
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
_focusNodes[index + 1].requestFocus();
} else {
_focusNodes[index].unfocus();
}
},
onTapOutside: (event) {
_focusNodes[index].unfocus();
},
),
);
},
);
focusNode: _focusNodes[index]
:每個 TextField 綁定FocusNode
keyboardType: TextInputType.number
:自動顯示數字鍵盤
maxLength: 1
:一次只能輸入一個字,適合輸入驗證碼textAlign: TextAlign.center
:輸入的驗證碼水平置中inputFormatters
:設定TextField
能輸入的限制FilteringTextInputFormatter.digitsOnly
:只能輸入數字
decoration
counterText: ''
:當搭配maxLength
時,counterText
會顯示當前字數 / 最大字數
的統計,但用於驗證碼輸入時則不需要,因此設定為 empty stringborder: OutlineInputBorder()
:驗證輸入為圓框
Line 47
onChanged: (value) {
if (value.isEmpty) {
return;
}
_codes[index] = value;
if (index < 5) {
_focusNodes[index + 1].requestFocus();
} else {
_focusNodes[index].unfocus();
}
},
- 當輸入時,會將值寫入
_codes
array - 當不是
最後一個
驗證碼時,會自動跳到下一個驗證碼 - 當輸入完最一個驗證碼時,自動隱藏
數字鍵盤
會在
輸入後
才取得index
,而不是輸入前
取得index
Line 60
onTapOutside: (event) {
_focusNodes[index].unfocus();
},
- 離開
TextField
自動隱藏數字鍵盤
Conclusion
FocusNode
實現較為繁瑣,實務上建議使用FocusTraversalGroup
較精簡