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

Flutter中如何实现两次输入PIN码的一致性校验?

PIN码设置与确认功能实现方案

需求说明

页面初始显示文本**“为尾号XXXX的卡片设置PIN码”,用户输入第4位PIN码后,自动切换为“重新输入PIN码确认”**。需对比两次输入的PIN码,若不一致则显示提示文本,全程使用同一个PinFieldAutoFill组件。

页面效果参考

设置PIN码页面

设置PIN码页面

重新输入PIN码页面

重新输入PIN码页面

现有代码

SetPinTestscreen 代码

class _SetPinTestscreenState extends State<SetPinTestscreen> {
  TextEditingController setPinController = TextEditingController();

  @override
  void initState() {
    Provider.of<PinValidationProvider>(context, listen: false).isPinSet = false;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MainTemplate(
      isFloatingButtonVisible: true,
      floatingButtonAction: () {},
      child: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Align(
              alignment: Alignment.centerLeft,
              child: Consumer<PinValidationProvider>(
                builder: (context, pinProvider, child) {
                  return Text(
                    pinProvider.isPinSet
                        ? 'Re-enter your PIN code to Confirm'
                        : 'Set PIN for your Card Ending With XXXX',
                    style: TextStyle(fontWeight: FontWeight.w700, fontSize: 30),
                  );
                },
              ),
            ),
            SizedBox(height: 20),
            Center(
              child: PinFieldAutoFill(
                decoration: UnderlineDecoration(
                  textStyle: Theme.of(context).textTheme.subtitle1.copyWith(
                        fontWeight: FontWeight.bold,
                        fontSize: 15,
                      ),
                  gapSpace: 8,
                  lineHeight: 1.2,
                  colorBuilder:
                      PinListenColorBuilder(Colors.black, Colors.grey.shade400),
                ),
                onCodeChanged: (value) {
                  Provider.of<PinValidationProvider>(context, listen: false)
                      .validatePin(value, setPinController);
                },
                codeLength: 4,
                cursor: Cursor(color: Colors.black, enabled: true),
                controller: setPinController,
                keyboardType: TextInputType.number,
              ),
            ),
            SizedBox(height: 20),
            Consumer<PinValidationProvider>(
                builder: (context, provider, child) {
              final textColor =
                  provider.isInvalidPin ? Colors.red : Colors.black;

              return provider.isPinSet
                  ? SizedBox.shrink()
                  : Text(
                      'Pin must be at least 4 digits. It must not be single digits like'
                      ' 1111 or digits in numerical order such as 1234',
                      style: TextStyle(
                        color: textColor,
                      ),
                    );
            }),
          ],
        ),
      ),
    );
  }
}

PinValidationProvider 类代码

class PinValidationProvider extends ChangeNotifier {
  bool _isInvalidPin = false;

  bool get isInvalidPin => _isInvalidPin;
  bool isPinSet = false;
  String pin = '';
  String confirmedPin = '';

  void validatePin(String pin,TextEditingController controller) {
    if (pin.length != 4) {
      _isInvalidPin = false;
      pin = pin ?? '';
    } else {
      confirmedPin = pin ?? '';
      controller.clear();
      if (_isSimpleSequence(pin) || _isRepeatedDigit(pin)) {
        _isInvalidPin = true;
      } else {
        _isInvalidPin = false;
        if (!isPinSet) {
          isPinSet = true;
        }
      }
    }
    Future.microtask(() {
      notifyListeners();
    });
  }

  bool _isSimpleSequence(String pin) {
    for (int i = 0; i < pin.length - 1; i++) {
      int digit1 = int.parse(pin[i]);
      int digit2 = int.parse(pin[i + 1]);

      if (digit2 - digit1 == 1) {
        return true;
      }
    }
    return false;
  }

  bool _isRepeatedDigit(String pin) {
    for (int i = 0; i < pin.length - 1; i++) {
      if (pin[i] == pin[i + 1]) {
        return true;
      }
    }
    return false;
  }
}

修改实现方案

1. 完善 PinValidationProvider 逻辑

新增PIN不一致状态标记,调整验证流程:

class PinValidationProvider extends ChangeNotifier {
  bool _isInvalidPin = false;
  bool _isPinMismatch = false; // 标记两次PIN是否不一致

  bool get isInvalidPin => _isInvalidPin;
  bool get isPinMismatch => _isPinMismatch;
  bool isPinSet = false;
  String _firstPin = ''; // 存储第一次输入的合法PIN

  void validatePin(String pin, TextEditingController controller) {
    if (pin.length != 4) {
      _isInvalidPin = false;
      _isPinMismatch = false;
      return;
    }

    // 第一次输入PIN:验证格式合法性
    if (!isPinSet) {
      if (_isSimpleSequence(pin) || _isRepeatedDigit(pin)) {
        _isInvalidPin = true;
        _isPinMismatch = false;
      } else {
        _isInvalidPin = false;
        _firstPin = pin;
        isPinSet = true;
        controller.clear();
      }
    } 
    // 第二次输入PIN:验证与第一次是否一致
    else {
      if (pin != _firstPin) {
        _isPinMismatch = true;
        controller.clear();
      } else {
        _isPinMismatch = false;
        // PIN一致,可添加提交到服务器等后续逻辑
      }
    }
    notifyListeners();
  }

  // 重置状态(用于重新设置PIN场景)
  void resetPinState() {
    isPinSet = false;
    _firstPin = '';
    _isInvalidPin = false;
    _isPinMismatch = false;
    notifyListeners();
  }

  bool _isSimpleSequence(String pin) {
    for (int i = 0; i < pin.length - 1; i++) {
      int digit1 = int.parse(pin[i]);
      int digit2 = int.parse(pin[i + 1]);
      if (digit2 - digit1 == 1) {
        return true;
      }
    }
    return false;
  }

  bool _isRepeatedDigit(String pin) {
    for (int i = 0; i < pin.length - 1; i++) {
      if (pin[i] == pin[i + 1]) {
        return true;
      }
    }
    return false;
  }
}

2. 更新 UI 层显示逻辑

添加PIN不一致提示,调整原有提示的显示条件:

class _SetPinTestscreenState extends State<SetPinTestscreen> {
  TextEditingController setPinController = TextEditingController();

  @override
  void initState() {
    Provider.of<PinValidationProvider>(context, listen: false).resetPinState();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MainTemplate(
      isFloatingButtonVisible: true,
      floatingButtonAction: () {},
      child: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Align(
              alignment: Alignment.centerLeft,
              child: Consumer<PinValidationProvider>(
                builder: (context, pinProvider, child) {
                  return Text(
                    pinProvider.isPinSet
                        ? '重新输入PIN码确认'
                        : '为尾号XXXX的卡片设置PIN码',
                    style: TextStyle(fontWeight: FontWeight.w700, fontSize: 30),
                  );
                },
              ),
            ),
            SizedBox(height: 20),
            Center(
              child: PinFieldAutoFill(
                decoration: UnderlineDecoration(
                  textStyle: Theme.of(context).textTheme.subtitle1.copyWith(
                        fontWeight: FontWeight.bold,
                        fontSize: 15,
                      ),
                  gapSpace: 8,
                  lineHeight: 1.2,
                  colorBuilder:
                      PinListenColorBuilder(Colors.black, Colors.grey.shade400),
                ),
                onCodeChanged: (value) {
                  if (value != null) {
                    Provider.of<PinValidationProvider>(context, listen: false)
                        .validatePin(value, setPinController);
                  }
                },
                codeLength: 4,
                cursor: Cursor(color: Colors.black, enabled: true),
                controller: setPinController,
                keyboardType: TextInputType.number,
              ),
            ),
            SizedBox(height: 20),
            Consumer<PinValidationProvider>(
                builder: (context, provider, child) {
              if (provider.isPinSet) {
                // 第二次输入时显示PIN不一致提示
                return provider.isPinMismatch
                    ? Text(
                        '两次输入的PIN码不一致,请重新输入',
                        style: TextStyle(color: Colors.red),
                      )
                    : SizedBox.shrink();
              } else {
                // 第一次输入时显示格式要求提示
                final textColor = provider.isInvalidPin ? Colors.red : Colors.black;
                return Text(
                  'PIN码必须为4位,不能是连续数字(如1234)或重复数字(如1111)',
                  style: TextStyle(color: textColor),
                );
              }
            }),
          ],
        ),
      ),
    );
  }
}

核心逻辑说明

  1. 第一次输入PIN:验证格式合法性(非连续/重复数字),合法则存储PIN并切换到确认状态,同时清空输入框。
  2. 第二次输入PIN:直接对比与第一次输入的PIN,不一致则显示红色提示并清空输入框;一致则完成验证流程。
  3. 通过ChangeNotifier实时通知UI更新状态,确保页面文本和提示的动态切换。

内容的提问来源于stack exchange,提问作者Mansoor Malik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 23:25:56