如何在Flutter中实现同款信用卡样式的FormField输入效果

实现代码
以下是封装完成的信用卡输入FormField组件,完全适配Flutter原生Form体系,所有字段的样式、装饰都支持自定义:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // 信用卡数据存储类 class CreditCardInfo { final String cardNumber; final String expireDate; final String cvv; final String cardHolder; CreditCardInfo({ required this.cardNumber, required this.expireDate, required this.cvv, required this.cardHolder, }); } // 卡号格式化器:每4位自动插入空格 class CardNumberInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { if (newValue.selection.baseOffset == 0) return newValue; String inputText = newValue.text.replaceAll(' ', ''); StringBuffer buffer = StringBuffer(); for (int i = 0; i < inputText.length; i++) { buffer.write(inputText[i]); int index = i + 1; if (index % 4 == 0 && index != inputText.length) { buffer.write(' '); } } String formatted = buffer.toString(); return newValue.copyWith( text: formatted, selection: TextSelection.collapsed(offset: formatted.length), ); } } // 有效期格式化器:自动添加/分隔,格式MM/YY class ExpireDateInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { String input = newValue.text.replaceAll('/', ''); if (input.length > 4) input = input.substring(0,4); StringBuffer buffer = StringBuffer(); for(int i=0; i<input.length; i++) { buffer.write(input[i]); if(i == 1 && input.length > 2) { buffer.write('/'); } } String formatted = buffer.toString(); return newValue.copyWith( text: formatted, selection: TextSelection.collapsed(offset: formatted.length), ); } } // 封装好的信用卡FormField组件 class CreditCardFormField extends FormField<CreditCardInfo> { CreditCardFormField({ super.key, super.autovalidateMode, super.enabled, super.validator, super.onSaved, // 可自定义的样式参数 InputDecoration? cardNumberDecoration, InputDecoration? expireDateDecoration, InputDecoration? cvvDecoration, InputDecoration? cardHolderDecoration, TextStyle? textStyle, }) : super( initialValue: CreditCardInfo(cardNumber: '', expireDate: '', cvv: '', cardHolder: ''), builder: (FormFieldState<CreditCardInfo> state) { return Column( mainAxisSize: MainAxisSize.min, children: [ // 卡号输入框 TextFormField( style: textStyle, decoration: cardNumberDecoration ?? const InputDecoration( labelText: '卡号', hintText: '**** **** **** ****', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, CardNumberInputFormatter(), LengthLimitingTextInputFormatter(19), // 16位数字+3个空格 ], onChanged: (val) { state.didChange(state.value?.copyWith(cardNumber: val)); }, validator: (val) { if(val == null || val.replaceAll(' ', '').length != 16) { return '请输入16位有效卡号'; } return null; }, ), const SizedBox(height: 16), Row( children: [ // 有效期输入框 Expanded( child: TextFormField( style: textStyle, decoration: expireDateDecoration ?? const InputDecoration( labelText: '有效期', hintText: 'MM/YY', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ExpireDateInputFormatter(), LengthLimitingTextInputFormatter(5), ], onChanged: (val) { state.didChange(state.value?.copyWith(expireDate: val)); }, validator: (val) { if(val == null || val.length !=5) { return '请输入有效有效期'; } return null; }, ), ), const SizedBox(width: 16), // CVV输入框 Expanded( child: TextFormField( style: textStyle, decoration: cvvDecoration ?? const InputDecoration( labelText: 'CVV', hintText: '***', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(3), ], onChanged: (val) { state.didChange(state.value?.copyWith(cvv: val)); }, validator: (val) { if(val == null || val.length !=3) { return '请输入3位CVV'; } return null; }, ), ), ], ), const SizedBox(height: 16), // 持卡人姓名输入框 TextFormField( style: textStyle, decoration: cardHolderDecoration ?? const InputDecoration( labelText: '持卡人姓名', hintText: '姓名拼音', border: OutlineInputBorder(), ), textCapitalization: TextCapitalization.characters, onChanged: (val) { state.didChange(state.value?.copyWith(cardHolder: val)); }, validator: (val) { if(val == null || val.isEmpty) { return '请输入持卡人姓名'; } return null; }, ), if(state.hasError) Padding( padding: const EdgeInsets.only(top: 8), child: Text( state.errorText!, style: const TextStyle(color: Colors.red, fontSize: 12), ), ) ], ); } ); } // 给CreditCardInfo加copyWith方法方便更新 extension CreditCardInfoCopy on CreditCardInfo { CreditCardInfo copyWith({ String? cardNumber, String? expireDate, String? cvv, String? cardHolder, }) { return CreditCardInfo( cardNumber: cardNumber ?? this.cardNumber, expireDate: expireDate ?? this.expireDate, cvv: cvv ?? this.cvv, cardHolder: cardHolder ?? this.cardHolder, ); } }
使用方式
直接将组件放入Flutter原生Form组件中使用即可,提交表单时可以直接拿到完整的信用卡信息:
Form( child: Column( children: [ CreditCardFormField( // 可在这里自定义各个输入框的样式,比如修改label样式、边框等 // cardNumberDecoration: InputDecoration(...), // textStyle: TextStyle(...), ), ElevatedButton( onPressed: () { if(Form.of(context).validate()) { Form.of(context).save(); // 这里获取到完整的信用卡信息 } }, child: const Text('提交'), ) ], ), )
内容的提问来源于stack exchange,提问作者Fsd Ramjan
相关产品推荐
相关产品推荐

