实现Flutter离线验证码组件

本文介绍了如何在Flutter中实现一个离线验证码组件,通过创建StatefulWidget,随机生成英文字符和数字,然后绘制到图像上。详细步骤包括组件类的创建、随机字符生成以及画图代码展示。提供了完整源码和学习资源链接。
在做一个项目的时候发现关于离线验证码的组件真的少,经过一番面向搜索引擎编程【百度】以后发现了一个非常小众,很少人使用的一个包hb_check_code: ^0.0.2( https://pub.flutter-io.cn/packages/hb_check_code);在此感谢这个大佬的实现,我便在大佬的肩上接着弄成自己项目需要的样子。

因为原本项目的验证码控件是使用原生Android的方式实现的,后面我对照了一下跟大佬的实现方法其实是差不多的,但是太久没有用上Flutter跟Dart都生疏了。
话不多说先上图片让大家看看:

现在我们来解析要想实现一直这个随机验证码图片要经历那些过程:
1、创建空文件快捷生成一个StatefulWidget组件类;
2、随机生成英文字符跟数字;
3、将这些随机生成的字符跟用来模糊数据的点与横线画出来。
不要打我,步骤就是这么简单,实现才是重点

首先实现我们先来生成一个简单组件类--就随便取个名字吧。【VerifyCode】

class VerifyCode extends StatefulWidget {
 final int vCodeNum;  //用来决定是生成多少位的验证码,默认4位
 final double width;  //设置控件宽度 默认70
 final double height; //设置控件高度 默认25
 final Color backgroundColor; //设置背景色
 //回调---用于获取随机的数据
 final ValueChanged<String> verifyCallback;
 const VerifyCode({Key key, this.vCodeNum=4, this.width=70, this.height=25, this.backgroundColor, this.verifyCallback}) : super(key: key);


 @override
 _VerifyCodeState createState() => _VerifyCodeState();
}

class _VerifyCodeState extends State<VerifyCode> {

 @override
 Widget build(BuildContext context) {}
}

上面就是简单是实现了一个验证码组件该有的东西,接下来我们来实现随机生成英文字符跟数字的方法:

//先定义一个非常low的常量
const _charsAll = ['1', '2', '3', '4', '5', '6',
 '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',  'j',
 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
 'X', 'Y', 'Z'];
 
 //然后定义一个随机生成字符串的方法
 String getRandomString(){
   String code = "";
   for (var i = 0; i < widget.vCodeNum; i++) {
     code = code +  _charsAll[Random().nextInt(_charsAll.length)];
   }
   return code;
 }

是不是非常low,慢慢来,后面会把源码都完整贴上。让大家自己去做修改

接下来就是开始画图了,话不多说直接上代码:

//随机生成绘图数据
//要是觉得干扰不够,你可以多画点线或者多画点。直接把widget.vCodeNum 这个参数另外定义一个用来控制线或者点数量的值
 Map getRandomData(String srt) {
   // list
   List list = srt.split("");
   // X坐标
   double x = 0.0;
   // 最大字体大小
   double maxFontSize = 25.0;
   //将painter保存起来,先计算出位置
   List mList = [];
   for (String item in list) {
    ...
   }
   double offsetX = (widget.width - x) / 2;
   List dotData = [];
   List lineData = [];
   //绘制干扰点
   for (var i = 0; i < widget.vCodeNum; i++) {
    ...
   }

   //绘制干扰线
   for (var i = 0; i < widget.vCodeNum; i++) {
    ...
   }

   Map checkCodeDrawData = {
     "painterData": mList,
     "offsetX": offsetX,
     "dotData": dotData,
     "lineData": lineData,
   };
   return checkCodeDrawData;
 }

//数据绘制
class VerifyCodePainter extends CustomPainter {
 final Map drawData;

 VerifyCodePainter({
   @required this.drawData,
 });

//画笔定义
 Paint _paint = new Paint()
   ..color = Colors.grey
   ..strokeCap = StrokeCap.square
   ..isAntiAlias = true
   ..strokeWidth = 1.0
   ..style = PaintingStyle.fill;

 @override
 void paint(Canvas canvas, Size size) {
   List mList = drawData["painterData"];

   double offsetX = drawData["offsetX"];
   //为了能居中显示移动画布
   canvas.translate(offsetX, 0);
   //从Map中取出值,直接绘制
   for (var item in mList) {
     ..
   }
   // //将画布平移回去
   canvas.translate(-offsetX, 0);
   List dotData = drawData["dotData"];
   for (var item in dotData) {
    //干扰点的绘制
    ...
   }

   List lineData = drawData["lineData"];
   for (var item in lineData) {
     //干扰线的绘制
     ...
   }
 }

 @override
 bool shouldRepaint(CustomPainter oldDelegate) {
   return this != oldDelegate;
 }
}

这样整体的工作就完成的差不多了,现在就差把整个组件实现起来;剩下的我就把完整的代码附上把毕竟摸鱼的人有什么坏心眼呢?

文件名:verifycode_widget.dart 欢迎自取有需要的自己改吧! 再次还是感谢【尤先森】大佬。hb_check_code: ^0.0.2(https://pub.flutter-io.cn/packages/hb_check_code

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_tyyjk/util/log_utils.dart';


const _charsAll = ['1', '2', '3', '4', '5', '6',
  '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',  'j',
  'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
  'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
  'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
  'X', 'Y', 'Z'];


class VerifyCode extends StatefulWidget {
  final int vCodeNum;
  final double width;
  final double height;
  final Color backgroundColor;
  //回调
  final ValueChanged<String> verifyCallback;
  const VerifyCode({Key key, this.vCodeNum=4, this.width=70, this.height=25, this.backgroundColor, this.verifyCallback}) : super(key: key);


  @override
  _VerifyCodeState createState() => _VerifyCodeState();
}

class _VerifyCodeState extends State<VerifyCode> {

  String _rdStr = '';
  double maxWidth = 0.0;
  Map _drawData;

  String getRandomString(){
    String code = "";
    for (var i = 0; i < widget.vCodeNum; i++) {
      code = code +  _charsAll[Random().nextInt(_charsAll.length)];
    }
    return code;
  }

  //随机生成绘图数据
  Map getRandomData(String srt) {
    // list
    List list = srt.split("");
    // X坐标
    double x = 0.0;
    // 最大字体大小
    double maxFontSize = 25.0;
    //将painter保存起来,先计算出位置
    List mList = [];
    for (String item in list) {
      Color color = Color.fromARGB(255, Random().nextInt(255),
          Random().nextInt(255), Random().nextInt(255));
      int fontWeight = Random().nextInt(9);
      TextSpan span = TextSpan(
          text: item,
          style: TextStyle(
              color: color,
              fontWeight: FontWeight.values[fontWeight],
              fontSize: maxFontSize - Random().nextInt(10)));
      TextPainter painter =
      TextPainter(text: span, textDirection: TextDirection.ltr);
      painter.layout();
      double y =
          Random().nextInt(widget.height.toInt()).toDouble() - painter.height;
      if (y < 0) {
        y = 0;
      }
      Map strMap = {"painter": painter, "x": x, "y": y};
      mList.add(strMap);
      x += painter.width + 3;
    }
    double offsetX = (widget.width - x) / 2;
    List dotData = [];
    List lineData = [];
    //绘制干扰点
    for (var i = 0; i < widget.vCodeNum; i++) {
      int r = Random().nextInt(255);
      int g = Random().nextInt(255);
      int b = Random().nextInt(255);
      double x = Random().nextInt(widget.width.toInt() - 5).toDouble();
      double y = Random().nextInt(widget.height.toInt() - 5).toDouble();
      double dotWidth = Random().nextInt(6).toDouble();
      Color color = Color.fromARGB(255, r, g, b);
      Map dot = {"x": x, "y": y, "dotWidth": dotWidth, "color": color};
      dotData.add(dot);
    }

    //绘制干扰线
    for (var i = 0; i < widget.vCodeNum; i++) {
      int r = Random().nextInt(255);
      int g = Random().nextInt(255);
      int b = Random().nextInt(255);
      double x = Random().nextInt(widget.width.toInt() - 5).toDouble();
      double y = Random().nextInt(widget.height.toInt() - 5).toDouble();
      double lineLength = Random().nextInt(20).toDouble();
      Color color = Color.fromARGB(255, r, g, b);
      Map line = {"x": x, "y": y, "lineLength": lineLength, "color": color};
      lineData.add(line);
    }

    Map checkCodeDrawData = {
      "painterData": mList,
      "offsetX": offsetX,
      "dotData": dotData,
      "lineData": lineData,
    };
    return checkCodeDrawData;
  }

  @override
  void initState() {
    // TODO: implement initState
    _rdStr = getRandomString();
    LogUtil.e(_rdStr);
    _drawData = getRandomData(_rdStr);
    //计算最大宽度做自适应
    maxWidth = getTextSize("8" * _rdStr.length,
        TextStyle(fontWeight: FontWeight.values[8], fontSize: 25))
        .width;
    //数据回调
    widget.verifyCallback(_rdStr);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {


    return Container(
        color: widget.backgroundColor,
        width: maxWidth > widget.width ? maxWidth : widget.width,
        height: widget.height,
        child: InkWell(
          onTap: (){
            setState(() {
              _rdStr = getRandomString();
              _drawData = getRandomData(_rdStr);
              //数据回调
              widget.verifyCallback(_rdStr);
            });
          },
          child: CustomPaint(
            painter: VerifyCodePainter(drawData: _drawData),
          ),
        ));
  }

  Size getTextSize(String text, TextStyle style) {
    final TextPainter textPainter = TextPainter(
        text: TextSpan(text: text, style: style),
        maxLines: 1,
        textDirection: TextDirection.ltr)
      ..layout(minWidth: 0, maxWidth: double.infinity);
    return textPainter.size;
  }
}

class VerifyCodePainter extends CustomPainter {
  final Map drawData;

  VerifyCodePainter({
    @required this.drawData,
  });

  Paint _paint = new Paint()
    ..color = Colors.grey
    ..strokeCap = StrokeCap.square
    ..isAntiAlias = true
    ..strokeWidth = 1.0
    ..style = PaintingStyle.fill;

  @override
  void paint(Canvas canvas, Size size) {
    List mList = drawData["painterData"];

    double offsetX = drawData["offsetX"];
    //居中显示移动画布
    canvas.translate(offsetX, 0);
    //从Map中取出值,直接绘制
    for (var item in mList) {
      TextPainter painter = item["painter"];
      double x = item["x"];
      double y = item["y"];
      painter.paint(
        canvas,
        Offset(x, y),
      );
    }
    // //将画布平移回去
    canvas.translate(-offsetX, 0);
    List dotData = drawData["dotData"];
    for (var item in dotData) {
      double x = item["x"];
      double y = item["y"];
      double dotWidth = item["dotWidth"];
      Color color = item["color"];
      _paint.color = color;
      canvas.drawOval(Rect.fromLTWH(x, y, dotWidth, dotWidth), _paint);
    }

    List lineData = drawData["lineData"];
    for (var item in lineData) {
      double x = item["x"];
      double y = item["y"];
      double lineLength = item["lineLength"];
      Color color = item["color"];
      _paint.color = color;
      canvas.drawLine(Offset(x,y),Offset(x+lineLength,y), _paint);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return this != oldDelegate;
  }
}

最后

为了让大家更好的去学习和提升自己,我和几位大佬一起收录整理的 Flutter进阶资料以及Android学习PDF+架构视频+面试文档+源码笔记 ,并且还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料……

这些都是我闲暇时还会反复翻阅的精品资料。可以有效的帮助大家掌握知识、理解原理。当然你也可以拿去查漏补缺,提升自身的竞争力。
如果你有需要的话,可以前往 GitHub 自行查阅。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值