You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 02:10:15