Flutter需求:实现含卡号、exp date、CVV的单TextField信用卡输入框
Flutter 实现单行多段信用卡输入 TextField
要实现一个特殊的信用卡输入框,在单个TextField内将卡号、有效期(exp date)、CVV码放在同一行,视觉上呈现分段效果。核心思路是通过TextEditingController监听输入并实时格式化文本,同时控制光标位置与输入限制,再通过装饰模拟分段视觉样式,以下是完整实现方案:
完整代码实现
import 'package:flutter/material.dart'; class CardInputField extends StatefulWidget { const CardInputField({Key? key}) : super(key: key); @override State<CardInputField> createState() => _CardInputFieldState(); } class _CardInputFieldState extends State<CardInputField> { final TextEditingController _controller = TextEditingController(); // 定义各字段最大输入长度 static const int _cardNumberMaxLength = 16; static const int _expDateMaxLength = 4; static const int _cvvMaxLength = 3; @override void initState() { super.initState(); // 添加输入监听,实时格式化文本 _controller.addListener(_formatCardInput); } @override void dispose() { _controller.dispose(); super.dispose(); } void _formatCardInput() { // 过滤所有非数字字符 String rawText = _controller.text.replaceAll(RegExp(r'[^0-9]'), ''); String formattedText = ''; int cursorPosition = _controller.selection.end; // 处理卡号部分:前16位,每4位加空格分隔 if (rawText.isNotEmpty) { String cardNumber = rawText.substring(0, rawText.length > _cardNumberMaxLength ? _cardNumberMaxLength : rawText.length); for (int i = 0; i < cardNumber.length; i++) { formattedText += cardNumber[i]; if ((i + 1) % 4 == 0 && i != cardNumber.length - 1) { formattedText += ' '; // 光标在分隔符后时,同步调整位置 if (cursorPosition > i + 1) cursorPosition++; } } } // 处理有效期部分:接下来4位,格式化为MM/YY if (rawText.length > _cardNumberMaxLength) { int expStart = _cardNumberMaxLength; int expEnd = expStart + _expDateMaxLength; String expDate = rawText.substring(expStart, rawText.length > expEnd ? expEnd : rawText.length); if (formattedText.isNotEmpty) formattedText += ' | '; for (int i = 0; i < expDate.length; i++) { formattedText += expDate[i]; if (i == 1 && expDate.length > 2) { formattedText += '/'; if (cursorPosition > expStart + i + 1) cursorPosition++; } } } // 处理CVV部分:最后3位 if (rawText.length > _cardNumberMaxLength + _expDateMaxLength) { int cvvStart = _cardNumberMaxLength + _expDateMaxLength; String cvv = rawText.substring(cvvStart, rawText.length > cvvStart + _cvvMaxLength ? cvvStart + _cvvMaxLength : rawText.length); if (formattedText.isNotEmpty) formattedText += ' | '; formattedText += cvv; } // 更新文本与光标,避免循环触发监听 if (_controller.text != formattedText) { _controller.value = TextEditingValue( text: formattedText, selection: TextSelection.collapsed(offset: cursorPosition.clamp(0, formattedText.length)), ); } } @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 16), child: TextField( controller: _controller, keyboardType: TextInputType.number, // 计算总长度:卡号16+3空格 + 有效期4+1/ + CVV3 + 两个|分隔符的空格 maxLength: 16 + 3 + 4 + 1 + 3 + 4, decoration: InputDecoration( hintText: 'XXXX XXXX XXXX XXXX | MM/YY | XXX', hintStyle: TextStyle( color: Colors.grey[400], fontSize: 16, ), focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Colors.blue, width: 2), ), enabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: Colors.grey[300]!, width: 2), ), counterText: '', // 隐藏长度计数器 ), inputFormatters: [ FilteringTextInputFormatter.digitsOnly, // 只允许输入数字 ], ), ); } }
关键实现说明
- 输入格式化:实时过滤非数字字符,按卡号、有效期、CVV的顺序拆分内容,自动添加分隔符与格式(如卡号每4位空格、有效期MM/YY)
- 光标控制:添加分隔符时同步调整光标位置,避免格式化后光标跳转到错误位置
- 输入限制:通过
FilteringTextInputFormatter限制仅数字输入,同时设置总长度上限,确保各字段不超过最大长度 - 视觉样式:通过
hintText模拟分段提示,若需要更贴合示例的分段边框样式,可自定义InputDecoration的prefix/suffix或嵌套Container实现
内容的提问来源于stack exchange,提问作者AkMax
相关产品推荐
相关产品推荐

