柚子快報邀請碼778899分享:Flutter 驗證碼輸入框
柚子快報邀請碼778899分享:Flutter 驗證碼輸入框
前言:
驗證碼輸入框很常見:處理不好 bug也會比較多 想實現(xiàn)方法很多,這里列舉一種完美方式,完美兼容 軟鍵盤粘貼方式
效果如下:
之前使用 uniapp 的方式實現(xiàn)過一次 兩種方式(原理相同):
input 驗證碼 密碼 輸入框_input密碼輸入框-CSDN博客文章瀏覽閱讀3.9k次,點贊3次,收藏6次。前言:uniapp 在做需求的時候,經(jīng)常會遇到;驗證碼輸入框 或者 密碼輸框 自定義樣式輸入框 或者 格式化顯示 銀行卡 手機號碼等等:這里總結(jié)了兩種 常用的實現(xiàn)方式;從這兩種實現(xiàn)方式 其實也能延伸出其他的顯示 方式;先看樣式: 自己實現(xiàn) 光標(biāo)閃爍動畫第一種:可以識別 獲得焦點 失去焦點第一種實現(xiàn)的思路: 實際上就是,下層的真實 input 負(fù)責(zé)響應(yīng)系統(tǒng)的輸入,上面一層負(fù)責(zé)顯示 應(yīng)為輸入框在手機端會 出現(xiàn)長按 學(xué)著 復(fù)制等等 輸入框自帶屬..._input密碼輸入框https://blog.csdn.net/nicepainkiller/article/details/124384995?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171723341916800226511048%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171723341916800226511048&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-124384995-null-null.nonecase&utm_term=input&spm=1018.2226.3001.4450
實現(xiàn)原理拆解:
輸入框區(qū)域我們分割成兩層:
6個黃色的區(qū)域 僅僅做展示,中間的黑色是一個動畫 模擬光標(biāo)閃爍 或者 展示 輸入的數(shù)字最上層蓋一個 輸入框控件 接收輸入事件,設(shè)置透明度 0.00001,設(shè)置不支持長按 選取復(fù)制,僅僅支持?jǐn)?shù)字
這樣一來就很明了, 邏輯也很簡單
?具體實現(xiàn):
要實現(xiàn) 軟鍵盤的 填充事件,所以我們需要動態(tài)監(jiān)聽 輸入事件
@override
void initState() {
// TODO: implement initState
super.initState();
// 自動彈出軟鍵盤
Future.delayed(Duration.zero, () {
FocusScope.of(context).requestFocus(_focusNode);
});
// 監(jiān)聽粘貼事件
_textEditingController.addListener(() {
if (Clipboard.getData('text/plain') != null) {
Clipboard.getData('text/plain').then((value) {
if (value != null && value.text != null) {
if (value.text!.isNotEmpty && value.text!.length == 6) {
if (RegExp(AppRegular.numberAll).firstMatch(value.text!) !=
null) {
_textEditingController.text = value!.text!;
//取完值 置為 null
Clipboard.setData(const ClipboardData(text: ''));
//設(shè)置輸入框光標(biāo)到末尾 防止某些情況下 光標(biāo)跑到前面,鍵盤無法刪除輸入字符
_textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: _textEditingController.text.length),
);
}
}
}
});
}
setState(() {
_arrayCode = List
for (int i = 0; i < _textEditingController.value.text.length; i++) {
_arrayCode[i] = _textEditingController.value.text.substring(i, i + 1);
}
});
if (_textEditingController.value.text.length == 6) {
//防止重復(fù)觸發(fā) 回調(diào)事件
if (!_triggerState) {
_triggerState = true;
AppScreen.showToast('輸入完成:${_textEditingController.value.text}');
widget.onComplete(_textEditingController.value.text);
}
} else {
_triggerState = false;
}
});
} 輸入框的設(shè)置,禁止長按 child: TextField(
enableInteractiveSelection: false, // 禁用長按復(fù)制功
maxLength: widget.length,
focusNode: _focusNode,
maxLines: 1,
controller: _textEditingController,
style: AppTextStyle.textStyle_32_333333,
inputFormatters: [InputFormatter(AppRegular.numberAll)],
decoration: const InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
disabledBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
border: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
counterText: '', //取消文字計數(shù)器
),
) 頁面動畫的展示,F(xiàn)adeTransition 為了性能優(yōu)化到我們動畫縮小到最小范圍 class InputFocusWidget extends StatefulWidget {
const InputFocusWidget({Key? key}) : super(key: key);
@override
State
}
class _InputFocusWidgetState extends State
with TickerProviderStateMixin {
late AnimationController controller;
late Animation
@override
void initState() {
// TODO: implement initState
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 600), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
controller.repeat(min: 0, max: 1, reverse: true);
}
@override
void dispose() {
controller.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animation,
child: Container(
color: Colors.green,
width: double.infinity,
height: double.infinity,
),
);
}
}
完整代碼:
?因為里面使用到我自己封裝的一些工具,用的時候需要你轉(zhuǎn)成自己的
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:game/utils/app_screen.dart';
import 'package:game/wrap/extension/extension.dart';
import 'package:game/wrap/overlay/app_overlay.dart';
import '../const/app_regular.dart';
import '../const/app_textStyle.dart';
import 'input_formatter.dart';
class InputWithCode extends StatefulWidget {
final int length;
final ValueChanged
const InputWithCode(
{required this.length, required this.onComplete, Key? key})
: super(key: key);
@override
State
}
class _InputWithCodeState extends State
final TextEditingController _textEditingController = TextEditingController();
bool _triggerState = false;
late List
final FocusNode _focusNode = FocusNode();
@override
void initState() {
// TODO: implement initState
super.initState();
// 自動彈出軟鍵盤
Future.delayed(Duration.zero, () {
FocusScope.of(context).requestFocus(_focusNode);
});
// 監(jiān)聽粘貼事件
_textEditingController.addListener(() {
if (Clipboard.getData('text/plain') != null) {
Clipboard.getData('text/plain').then((value) {
if (value != null && value.text != null) {
if (value.text!.isNotEmpty && value.text!.length == 6) {
if (RegExp(AppRegular.numberAll).firstMatch(value.text!) !=
null) {
_textEditingController.text = value!.text!;
Clipboard.setData(const ClipboardData(text: ''));
_textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: _textEditingController.text.length),
);
}
}
}
});
}
setState(() {
_arrayCode = List
for (int i = 0; i < _textEditingController.value.text.length; i++) {
_arrayCode[i] = _textEditingController.value.text.substring(i, i + 1);
}
});
if (_textEditingController.value.text.length == 6) {
if (!_triggerState) {
_triggerState = true;
AppScreen.showToast('輸入完成:${_textEditingController.value.text}');
widget.onComplete(_textEditingController.value.text);
}
} else {
_triggerState = false;
}
});
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
child: Stack(
children: [
Center(
child: Row(
children: _arrayCode
.asMap()
.map(
(index, value) => MapEntry(
index,
Container(
width: 80.cale,
height: 80.cale,
margin: EdgeInsets.symmetric(horizontal: 10.cale),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 3.cale,
color: value != ''
? Colors.amberAccent
: Colors.amberAccent.withOpacity(0.5),
),
),
),
child: index != _textEditingController.value.text.length
? Center(
child: Text(
value,
style: AppTextStyle.textStyle_40_1A1A1A_Bold,
),
)
: Center(
child: SizedBox(
width: 3.cale,
height: 40.cale,
child: const InputFocusWidget(),
),
),
),
),
)
.values
.toList(),
),
),
Opacity(
opacity: 0.0001,
child: SizedBox(
height: double.infinity,
width: double.infinity,
child: TextField(
enableInteractiveSelection: false, // 禁用長按復(fù)制功
maxLength: widget.length,
focusNode: _focusNode,
maxLines: 1,
controller: _textEditingController,
style: AppTextStyle.textStyle_32_333333,
inputFormatters: [InputFormatter(AppRegular.numberAll)],
decoration: const InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
disabledBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
border: OutlineInputBorder(
borderSide:
BorderSide(width: 0, color: Colors.transparent)),
counterText: '', //取消文字計數(shù)器
),
),
),
),
],
),
);
}
}
class InputFocusWidget extends StatefulWidget {
const InputFocusWidget({Key? key}) : super(key: key);
@override
State
}
class _InputFocusWidgetState extends State
with TickerProviderStateMixin {
late AnimationController controller;
late Animation
@override
void initState() {
// TODO: implement initState
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 600), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
controller.repeat(min: 0, max: 1, reverse: true);
}
@override
void dispose() {
controller.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animation,
child: Container(
color: Colors.green,
width: double.infinity,
height: double.infinity,
),
);
}
}
使用:
?控件名稱:InputWithCode?length:驗證碼長度onComplete: 輸入完成回調(diào)
Container(
child: InputWithCode(
length: 6,
onComplete: (code) => {
print('InputWithCode:$code'),
},
),
width: double.infinity,
height: 200.cale,
),
柚子快報邀請碼778899分享:Flutter 驗證碼輸入框
好文閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。