jjzjj

Flutter-自定义短信验证码

龙之音 2023-03-28 原文

效果图(Flutter版本)

简介

前几天我发布了一个Android版本的短信验证码,今天发布Flutter版本,其实实现思路和原生版本是一模一样,可以说是直接把原生的绘制代码复制粘贴到Flutter项目中,kt修改为dart,实现样式还是下面四种:

  • 表格类型
  • 方块类型
  • 横线类型
  • 圈圈类型

所以这里就不在阐述实现思路了,你也可以直接查看Android版本,点击
Android-自定义短信验证码

这里直接上全部代码,一把梭~


代码

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

/*
 * 模式
 */
enum CodeMode {
  //文字
  text
}

/*
 * 样式
 */
enum CodeStyle {
  //表格
  form,
  //方块
  rectangle,
  //横线
  line,
  //圈圈
  circle
}

/*
 * 验证码
 */
class CodeWidget extends StatefulWidget {
  CodeWidget({
    Key? key,
    this.maxLength = 4,
    this.height = 50,
    this.mode = CodeMode.text,
    this.style = CodeStyle.form,
    this.codeBgColor = Colors.transparent,
    this.borderWidth = 2,
    this.borderColor = Colors.grey,
    this.borderSelectColor = Colors.red,
    this.borderRadius = 3,
    this.contentColor = Colors.black,
    this.contentSize = 16,
    this.itemWidth = 50,
    this.itemSpace = 16,
  }) : super(key: key) {
    //如果是表格样式,就不设置Item之间的距离
    if (style == CodeStyle.form) {
      itemSpace = 0;
    }
  }

  late int maxLength;

  //高度
  late double height;

  //验证码模式
  late CodeMode mode;

  //验证码样式
  late CodeStyle style;

  //背景色
  late Color codeBgColor;

  //边框宽度
  late double borderWidth;

  //边框默认颜色
  late Color borderColor;

  //边框选中颜色
  late Color borderSelectColor;

  //边框圆角
  late double borderRadius;

  //内容颜色
  late Color contentColor;

  //内容大小
  late double contentSize;

  // 单个Item宽度
  late double itemWidth;

  //Item之间的间隙
  late int itemSpace;

  @override
  State<CodeWidget> createState() => _CodeWidgetState();
}

class _CodeWidgetState extends State<CodeWidget> {
  FocusNode focusNode = FocusNode();

  TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      child: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          var size = Size(constraints.maxWidth, constraints.maxHeight);
          return CustomPaint(
            size: size,
            painter: CodeCustomPainter(
              widget.maxLength,
              size.width,
              size.height,
              widget.mode,
              widget.style,
              widget.codeBgColor,
              widget.borderWidth,
              widget.borderColor,
              widget.borderSelectColor,
              widget.borderRadius,
              widget.contentColor,
              widget.contentSize,
              widget.itemWidth,
              widget.itemSpace,
              focusNode,
              controller,
            ),
            child: TextField(
              //控制焦点
              focusNode: focusNode,
              controller: controller,
              //光标不显示
              showCursor: false,
              //光标颜色透明
              cursorColor: Colors.transparent,
              enableInteractiveSelection: false,
              //设置最大长度
              maxLength: widget.maxLength,
              //文字样式为透明
              style: const TextStyle(
                color: Colors.transparent,
              ),
              //只允许数据数字
              inputFormatters: [
                FilteringTextInputFormatter.digitsOnly,
              ],
              //弹出数字键盘
              keyboardType: TextInputType.number,
              //边框样式取消
              decoration: null,
            ),
          );
        },
      ),
    );
  }
}

class CodeCustomPainter extends CustomPainter {
  double width;
  double height;
  int maxLength;

  //验证码模式
  late CodeMode mode;

  //验证码样式
  late CodeStyle style;

  //背景色
  Color codeBgColor;

  //边框宽度
  double borderWidth;

  //边框默认颜色
  Color borderColor;

  //边框选中颜色
  Color borderSelectColor;

  //边框圆角
  double borderRadius;

  //内容颜色
  Color contentColor;

  //内容大小
  double contentSize;

  // 单个Item宽度
  double itemWidth;

  //Item之间的间隙
  int itemSpace;

  //焦点
  FocusNode focusNode;

  TextEditingController controller;

  //线路画笔
  late Paint linePaint;

  //文字画笔
  late TextPainter textPainter;

  //当前文字索引
  int currentIndex = 0;

  //左右间距值
  double space = 0;

  CodeCustomPainter(
    this.maxLength,
    this.width,
    this.height,
    this.mode,
    this.style,
    this.codeBgColor,
    this.borderWidth,
    this.borderColor,
    this.borderSelectColor,
    this.borderRadius,
    this.contentColor,
    this.contentSize,
    this.itemWidth,
    this.itemSpace,
    this.focusNode,
    this.controller,
  ) {
    linePaint = Paint()
      ..color = borderColor
      ..isAntiAlias = true
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = borderWidth;

    textPainter = TextPainter(
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
    );
  }

  @override
  void paint(Canvas canvas, Size size) {
    //当前索引(待输入的光标位置)
    currentIndex = controller.text.length;
    //Item宽度(这里判断如果设置了宽度并且合理就使用当前设置的宽度,否则平均计算)
    if (itemWidth != -1 &&
        (itemWidth * maxLength + itemSpace * (maxLength - 1)) <= width) {
      itemWidth = itemWidth;
    } else {
      itemWidth = ((width - itemSpace * (maxLength - 1)) / maxLength);
    }

    //计算左右间距大小
    space = (width - itemWidth * maxLength - itemSpace * (maxLength - 1)) / 2;

    //绘制样式
    switch (style) {
      //表格
      case CodeStyle.form:
        _drawFormCode(canvas, size);
        break;
      //方块
      case CodeStyle.rectangle:
        _drawRectangleCode(canvas, size);
        break;
      //横线
      case CodeStyle.line:
        _drawLineCode(canvas, size);
        break;
      //圈圈
      case CodeStyle.circle:
        _drawCircleCode(canvas, size);
        break;
      //TODO  拓展
    }

    //绘制文字内容
    _drawContentCode(canvas, size);
  }

  /*
   * 绘制表格样式
   */
  void _drawFormCode(Canvas canvas, Size size) {
    //绘制表格边框
    Rect rect = Rect.fromLTRB(space, 0, width - space, height);
    RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(borderRadius));
    linePaint.color = borderColor;
    canvas.drawRRect(rRect, linePaint);

    //绘制表格中间分割线
    for (int i = 1; i < maxLength; i++) {
      double startX = space + itemWidth * i + itemSpace * i;
      double startY = 0;
      double stopY = height;
      canvas.drawLine(Offset(startX, startY), Offset(startX, stopY), linePaint);
    }

    //绘制当前位置边框
    for (int i = 0; i < maxLength; i++) {
      if (currentIndex != -1 && currentIndex == i && focusNode.hasFocus) {
        //计算每个表格的左边距离
        double left = 0;
        if (i == 0) {
          left = (space + itemWidth * i);
        } else {
          left = ((space + itemWidth * i + itemSpace * i));
        }
        linePaint.color = borderSelectColor;
        //第一个
        if (i == 0) {
          RRect rRect = RRect.fromLTRBAndCorners(
              left, 0, left + itemWidth, height,
              topLeft: Radius.circular(borderRadius),
              bottomLeft: Radius.circular(borderRadius));
          canvas.drawRRect(rRect, linePaint);
        }
        //最后一个
        else if (i == maxLength - 1) {
          RRect rRect = RRect.fromLTRBAndCorners(
              left, 0, left + itemWidth, height,
              topRight: Radius.circular(borderRadius),
              bottomRight: Radius.circular(borderRadius));
          canvas.drawRRect(rRect, linePaint);
        }
        //其他
        else {
          RRect rRect =
              RRect.fromLTRBAndCorners(left, 0, left + itemWidth, height);
          canvas.drawRRect(rRect, linePaint);
        }
      }
    }
  }

  /*
   * 绘制方块样式
   */
  void _drawRectangleCode(Canvas canvas, Size size) {
    for (int i = 0; i < maxLength; i++) {
      double left = 0;
      if (i == 0) {
        left = space + i * itemWidth;
      } else {
        left = space + i * itemWidth + itemSpace * i;
      }
      Rect rect = Rect.fromLTRB(left, 0, left + itemWidth, height);
      RRect rRect =
          RRect.fromRectAndRadius(rect, Radius.circular(borderRadius));
      //当前光标样式
      if (currentIndex != -1 && currentIndex == i && focusNode.hasFocus) {
        linePaint.color = borderSelectColor;
        canvas.drawRRect(rRect, linePaint);
      }
      //默认样式
      else {
        linePaint.color = borderColor;
        canvas.drawRRect(rRect, linePaint);
      }
    }
  }

  /*
   * 绘制横线样式
   */
  void _drawLineCode(Canvas canvas, Size size) {
    for (int i = 0; i < maxLength; i++) {
      //当前选中状态
      if (controller.value.text.length == i && focusNode.hasFocus) {
        linePaint.color = borderSelectColor;
      }
      //默认状态
      else {
        linePaint.color = borderColor;
      }
      double startX = space + itemWidth * i + itemSpace * i;
      double startY = height - borderWidth;
      double stopX = startX + itemWidth;
      double stopY = startY;
      canvas.drawLine(Offset(startX, startY), Offset(stopX, stopY), linePaint);
    }
  }

  /*
   * 绘制圈圈样式
   */
  void _drawCircleCode(Canvas canvas, Size size) {
    for (int i = 0; i < maxLength; i++) {
      //当前绘制的圆圈的左x轴坐标
      double left = 0;
      if (i == 0) {
        left = space + i * itemWidth;
      } else {
        left = space + i * itemWidth + itemSpace * i;
      }
      //圆心坐标
      double cx = left + itemWidth / 2.0;
      double cy = height / 2.0;
      //圆形半径
      double radius = itemWidth / 5.0;
      //默认样式
      if (i >= currentIndex) {
        linePaint.style = PaintingStyle.fill;
        canvas.drawCircle(Offset(cx, cy), radius, linePaint);
      }
    }
  }

  /*
   * 绘制验证码文字
   */
  void _drawContentCode(Canvas canvas, Size size) {
    String textStr = controller.text;
    for (int i = 0; i < maxLength; i++) {
      if (textStr.isNotEmpty && i < textStr.length) {
        switch (mode) {
          case CodeMode.text:
            String code = textStr[i].toString();
            textPainter.text = TextSpan(
              text: code,
              style: const TextStyle(color: Colors.red, fontSize: 30),
            );
            textPainter.layout();
            double x = space +
                itemWidth * i +
                itemSpace * i +
                (itemWidth - textPainter.width) / 2;
            double y = (height - textPainter.height) / 2;
            textPainter.paint(canvas, Offset(x, y));
            break;
          //TODO  拓展
        }
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

如果滑动到这里,别忙走,请关注下"Android小样"公众号呗。

Github:https://github.com/yixiaolunhui/flutter_xy

本文由mdnice多平台发布

有关Flutter-自定义短信验证码的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  4. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  5. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  6. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  7. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  8. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  9. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  10. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

随机推荐