Flutter 组件抽取:验证码输入功能(CodeInputContainer)

2023-10-30

简介

验证码输入框,可选需要输入的验证码个数,输入达指定个数后自动回调

效果

在这里插入图片描述

范例

class _TestPageState extends State<TestPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CodeInputContainer')),
      body: Container(
        width: double.maxFinite,
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            CodeInputContainer(
              count: 4,
              phone: '18888888888',
              onRestart: () async {
                Function() cancel = MsgUtil.loading();
                await Future.delayed(const Duration(seconds: 1));
                cancel.call();
                MsgUtil.toast('验证码发送失败');
                return false;
              },
              onResult: (code) {
                MsgUtil.toast('验证码($code)正确');
              },
            ),
            const SizedBox(height: 30),
            CodeInputContainer(
              count: 6,
              phone: '18888888888',
              onRestart: () async {
                Function() cancel = MsgUtil.loading();
                await Future.delayed(const Duration(seconds: 1));
                cancel.call();
                MsgUtil.toast('验证码发送成功');
                return true;
              },
              onResult: (code) {
                MsgUtil.toast('验证码($code)错误');
              },
            ),
          ],
        ),
      ),
    );
  }
}

说明

自动维护页面切换、APP 退到后台、熄屏等情况下,重新进入页面时的倒计时对齐问题

代码

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// 验证码输入框
class CodeInputContainer extends StatefulWidget {
  final int count;
  final String phone;
  final Function(String code) onResult;
  /// 重新发起获取验证码
  /// [return] 是否发起"获取验证码"操作成功
  final Future<bool> Function() onRestart;

  const CodeInputContainer({
    super.key,
    required this.count,
    required this.phone,
    required this.onResult,
    required this.onRestart,
  });

  @override
  State createState() => _CodeInputContainerState();
}

class _CodeInputContainerState extends State<CodeInputContainer> with WidgetsBindingObserver {
  late final ValueNotifier<String> code = ValueNotifier('');
  late FocusNode inputFocus = FocusNode();

  bool restart = false;
  Timer? timer;
  final int seconds = 60;
  late final ValueNotifier<int> timeCount = ValueNotifier(seconds);
  DateTime? pausedTime;

  void startTimer() {
    timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      timeCount.value--;
      if (timeCount.value <= 0) {
        timer.cancel();
        timeCount.value = seconds;
        code.value = '';
        setState(() {
          restart = true;
        });
      }
    });
  }

  String handlePhone(String phone) {
    if (phone.length == 11) {
      return '${phone.substring(0, 3)} ${phone.substring(3, 7)} ${phone.substring(7, 11)}';
    } else {
      return phone;
    }
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      inputFocus.requestFocus();
      startTimer();
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    /// 适配页面切换、熄屏时倒计时混乱问题
    if (state == AppLifecycleState.resumed) {
      if (pausedTime != null) {
        int seconds = DateTime.now().difference(pausedTime!).inSeconds;
        pausedTime = null;
        timeCount.value = max(0, timeCount.value - seconds);
        startTimer();
      }
    } else if (state == AppLifecycleState.paused) {
      timer?.cancel();
      pausedTime = DateTime.now();
    }
  }

  @override
  void dispose() {
    super.dispose();
    WidgetsBinding.instance.removeObserver(this);
    timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: [
        const Text(
          '输入验证码',
          style: TextStyle(
            color: Colors.black,
            fontSize: 24,
            fontWeight: FontWeight.w600,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          '验证码已发送至 ${handlePhone(widget.phone)}',
          style: const TextStyle(color: Colors.grey, fontSize: 13),
        ),
        const SizedBox(height: 20),
        buildCodeInput(),
        GestureDetector(
          onTap: () {
            /// 点击时弹出输入键盘
            SystemChannels.textInput.invokeMethod('TextInput.show');
            inputFocus.requestFocus();
          },
          child: buildCodeView(),
        ),
        const SizedBox(height: 15),
        if (!restart)
          ValueListenableBuilder<int>(
            valueListenable: timeCount,
            builder: (context, value, child) {
              return Text(
                '$value 秒后可重新获取',
                style: const TextStyle(color: Colors.grey, fontSize: 13),
              );
            },
          ),
        if (restart)
          GestureDetector(
            onTap: () async {
              if (await widget.onRestart.call()) {
                setState(() {
                  restart = false;
                });
                startTimer();
              }
            },
            child: const Text(
              '重新发送',
              style: TextStyle(color: Colors.red, fontSize: 13),
            ),
          ),
      ],
    );
  }

  Widget buildCodeInput() {
    return SizedBox(
      height: 0,
      width: 0,
      child: TextField(
        controller: TextEditingController(text: code.value),
        focusNode: inputFocus,
        maxLength: widget.count,
        keyboardType: TextInputType.number,
        // 禁止长按复制
        enableInteractiveSelection: false,
        decoration: const InputDecoration(
          counterText: '',
          border: OutlineInputBorder(borderSide: BorderSide.none),
        ),
        inputFormatters: [
          // 只允许输入数字
          FilteringTextInputFormatter(RegExp("^[0-9]*\$"), allow: true)
        ],
        onChanged: (v) async {
          code.value = v;
          if (v.length == widget.count) widget.onResult.call(v);
        },
      ),
    );
  }

  Widget buildCodeView() {
    return ValueListenableBuilder<String>(
      valueListenable: code,
      builder: (context, value, child) {
        return GridView.count(
          padding: EdgeInsets.zero,
          crossAxisCount: widget.count,
          scrollDirection: Axis.vertical,
          physics: const NeverScrollableScrollPhysics(),
          shrinkWrap: true,
          crossAxisSpacing: 8,
          childAspectRatio: 0.95,
          children: List.generate(widget.count, (int i) => i).map((index) {
            return Container(
              alignment: Alignment.center,
              decoration: BoxDecoration(
                color: Colors.grey.shade200,
                border: ((index < widget.count && index == value.length) ||
                        (inputFocus.hasFocus && value.isEmpty && index == 0))
                    ? Border.all(width: 1, color: Colors.red)
                    : null,
                borderRadius: BorderRadius.circular(8),
              ),
              child: (value.length > index)
                  ? Text(
                      value[index],
                      style: const TextStyle(
                        color: Colors.black,
                        fontSize: 24,
                        fontWeight: FontWeight.w500,
                      ),
                    )
                  : null,
            );
          }).toList(),
        );
      },
    );
  }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter 组件抽取:验证码输入功能(CodeInputContainer) 的相关文章

随机推荐

  • GIT error: object file is empty?

    how to fix GIT error object file is empty 前几天在使用git status 产生了一个错误 经过多方搜索 找到一篇文章 现简述一下大意 原文 开始 当我尝试提交一个修改时 我得到了一个错误 erro
  • Servlet 规范和 Servlet 容器

    如果大家觉得文章有错误内容 欢迎留言或者私信讨论 前引 通过之前的学习我们知道浏览器发给服务器的 HTTP 请求在服务器端需要调用服务端的程序来处理 也就是我们写的 Java 类 一般来说不同请求对应不同的 Java 类 那么问题来了 HT
  • 解决vue中样式不起作用:样式穿透/深度选择器(/deep/)

    项目场景 提示 这里简述项目相关背景 解决vue中样式不起作用 样式穿透 深度选择器 deep 原因分析 提示 这里填写问题的分析 原因1 组件内部使用组件 添加了scoped属性 原因2 动态引入html 也添加了scoped属性 原因3
  • 工作中PTO和OOO是什么意思?

    PTO paid time off 带薪休假 其实就是我们所说的请年假 OOO out of office 就是字面意思 不在办公室 推荐一篇商务英文缩写介绍 ASAP OOO FYI 你都看得懂嗎 步入職場 一定要會的50個 商用縮寫
  • UE4(一)- 源码分析起点

    06 21 2020 UE4源码分析的起点 UE4文件结构 UE4 Editor UE4 C 项目文件夹结构 UE4 源码分析的起点 项目中的main函数 UObject 继承树 1 Gameplay类 Objects Actors Com
  • Nodejs开发(二)-项目打包和运行

    在Nodejs开发 一 Windows搭建Node环境搭建nodejs环境之后 接着进行项目的打包和运行 1 npm run build prod 首先管理员方式打开cmd 然后cd到项目所在文件夹 执行命令npm run build pr
  • NOKIA 刷机 6680

    使用NOKIA最新的PC套件6 82版可以在线刷机了 必须为6 82版 下载地址 http nds1 nokia com files support global phones software Nokia PC Suite 682 rel
  • 小兔鲜儿 - 微信登录

    目录 微信登录 登录方式 静态结构 获取登录凭证 获取手机号码 微信登录接口 生产环境 模拟手机登录 开发环境 用户信息持久化存储 涉及知识点 微信授权登录 文件上传 Store 状态管理等 微信登录 微信小程序的开放能力 允许开发者获取微
  • u盘安装CentOS(linux)的步骤(含双系统)

    为了学习新的知识 决定给自己的神舟本子装上CentOS系统 于是乎就自己在网上搜了教程 然而自己在安装的过程中还是出现了小问题 尤其是为了进入图形安装界面花了我很大的力气 经过多次尝试终于成功 每次装系统我的本子都会受到摧残 o 为了能给想
  • vs2015中cuda提示<<<>>>需要输入表达式

    在vs2015中写cuda代码进行编译时发现在调用核的时候 lt lt lt gt gt gt 总是提示有错误 编译提示输入表达式 但是编译是通过的 我的这个文件类型是cuda文件 cu 因为在vs中使用的是c 的语法提示 所以这个地方总是
  • Python数据库SQLite中的fetchone()、fetchMany()、fetchall()函数

    今天在练习python数据库的查询操作时 使用fetchone fetchMany fetchall 函数 出现了一些奇怪的现象 现在做如下记录 我想在同一个代码块中 使用fetchone 查询一条信息 使用fetchmany 3 查询3条
  • SQL Server中的登录名和用户名映射关系

    SQL Server 1 SQL Server 中 一个登录名可以映射多个数据库用户名 但是一个数据库用户名不能同时被两个数据库登录名映射 可以分别被A映射完 再被B映射 否则会干掉其中的一个 2 guest 用户是一个数据库用户名 是一个
  • oracle中的几种分区方式

    1 列表分区 1 1 分区技术实质可以把数据分摊到不同的物理位置 增加I O负载 提高检索效率 可用性 分区表可以跨越表空间 而普通表则不然 好处就是如果表的一个分区损坏 其他分区不会受到影响我们只需要修复损坏的分区即 可 1 2 创建li
  • 牛顿迭代法解非线性方程组(附C++代码)

    目录 一 公式介绍 二 应用环境 三 C 代码 实例说明 C 编译环境 C 代码 运行结果 特别注意 解决方法 一 公式介绍 牛顿迭代法基本公式 迭代出 面对非线性方程组问题 将上式变形 其中为 注 这里面的不再时代表一个值 而是代表关于变
  • SpringBoot---@DeleteMapping

    DeleteMapping 在编写代码时引用了 DeleteMapping 注解 DeleteMapping value remove courseNo public String remove PathVariable courseNo
  • linux中shell脚本命令使用详解

    文章目录 一 普通用户和超级用户 二 关于系统的操作 三 关于文件操作 3 1 ls显示文件 3 2 cd进出目录 3 3 mkdir创建目录 3 4 touch创建文件 3 5 cp复制命令 3 6 rm删除命令 3 7 cat命令 3
  • 注意力机制:CA - Coordinate Attention for Efficient Mobile Network Design(文末附代码)

    注意力机制 CA Coordinate Attention for Efficient Mobile Network Design 摘要 引言 具体而言 优势 相关工作 Mobile Network 架构 注意力机制 Coordinate
  • java 正则表达式 检测数学公式是否正确_java 正则表达式 检测数学公式是否正

    java 正则表达式 检测数学公式是否正 2021 02 05 13 33 36 简介 java中正则表达式基本用法的使用 1 Test01 java 使用正则表达式使代码变得非常简洁 2 TestMatcher01 java Matche
  • IBM近期扩充Watson认知API服务

    本文转载至 http www infoq com cn news 2016 03 watson cognitive apis 近期 IBM对Watson认知API服务进行了扩充 新增了情绪和语调分析API的测试版本 Watson的情绪分析A
  • Flutter 组件抽取:验证码输入功能(CodeInputContainer)

    简介 验证码输入框 可选需要输入的验证码个数 输入达指定个数后自动回调 效果 范例 class TestPageState extends State