在做一個專案的時候發現關於離線驗證碼的元件真的少,經過一番面向搜尋引擎程式設計【百度】以後發現了一個非常小眾,很少人使用的一個包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; }}
這樣整體的工作就完成的差不多了,現在就差把整個元件實現起來;剩下的我就把完整的程式碼附上把畢竟摸魚的人有什麼壞心眼呢?
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; }}
最後在這裡,我也準備了和大佬的交流筆記,還有一些大佬推薦的學習資料以及面試資料,除開大佬推薦的,我自己也準備了一些底層原理解析,專案實戰,面試專題。需要的可以私信我【資料】免費獲得。
底層原理+專案實戰+面試專題
1、底層原理(Android進階、架構設計、NDK、跨平臺、底層原始碼....)
2、專案實戰(一線網際網路大廠真實專案實戰訓練)
3、面試專題(大廠內推+簡歷最佳化+面試技巧+Android築基+分散式+開源框架+微服務架構+效能最佳化+JVM 等一線網際網路企業面試題)
資料對標對標阿里 P7,覆蓋 99% 網際網路公司技術要求