Flutter版本的自定義短信驗(yàn)證碼實(shí)現(xiàn)示例解析
效果圖(Flutter版本)

簡介
前幾天我發(fā)布了一個Android版本的短信驗(yàn)證碼,今天發(fā)布Flutter版本,其實(shí)實(shí)現(xiàn)思路和原生版本是一模一樣,可以說是直接把原生的繪制代碼復(fù)制粘貼到Flutter項(xiàng)目中,kt修改為dart,實(shí)現(xiàn)樣式還是下面四種:
- 表格類型
- 方塊類型
- 橫線類型
- 圈圈類型
所以這里就不在闡述實(shí)現(xiàn)思路了,你也可以直接查看Android版本,點(diǎn)擊
這里直接上全部代碼,一把梭~
代碼
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/*
* 模式
*/
enum CodeMode {
//文字
text
}
/*
* 樣式
*/
enum CodeStyle {
//表格
form,
//方塊
rectangle,
//橫線
line,
//圈圈
circle
}
/*
* 驗(yàn)證碼
*/
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) {
//如果是表格樣式,就不設(shè)置Item之間的距離
if (style == CodeStyle.form) {
itemSpace = 0;
}
}
late int maxLength;
//高度
late double height;
//驗(yàn)證碼模式
late CodeMode mode;
//驗(yàn)證碼樣式
late CodeStyle style;
//背景色
late Color codeBgColor;
//邊框?qū)挾?
late double borderWidth;
//邊框默認(rèn)顏色
late Color borderColor;
//邊框選中顏色
late Color borderSelectColor;
//邊框圓角
late double borderRadius;
//內(nèi)容顏色
late Color contentColor;
//內(nèi)容大小
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(
//控制焦點(diǎn)
focusNode: focusNode,
controller: controller,
//光標(biāo)不顯示
showCursor: false,
//光標(biāo)顏色透明
cursorColor: Colors.transparent,
enableInteractiveSelection: false,
//設(shè)置最大長度
maxLength: widget.maxLength,
//文字樣式為透明
style: const TextStyle(
color: Colors.transparent,
),
//只允許數(shù)據(jù)數(shù)字
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
//彈出數(shù)字鍵盤
keyboardType: TextInputType.number,
//邊框樣式取消
decoration: null,
),
);
},
),
);
}
}
class CodeCustomPainter extends CustomPainter {
double width;
double height;
int maxLength;
//驗(yàn)證碼模式
late CodeMode mode;
//驗(yàn)證碼樣式
late CodeStyle style;
//背景色
Color codeBgColor;
//邊框?qū)挾?
double borderWidth;
//邊框默認(rèn)顏色
Color borderColor;
//邊框選中顏色
Color borderSelectColor;
//邊框圓角
double borderRadius;
//內(nèi)容顏色
Color contentColor;
//內(nèi)容大小
double contentSize;
// 單個Item寬度
double itemWidth;
//Item之間的間隙
int itemSpace;
//焦點(diǎn)
FocusNode focusNode;
TextEditingController controller;
//線路畫筆
late Paint linePaint;
//文字畫筆
late TextPainter textPainter;
//當(dāng)前文字索引
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) {
//當(dāng)前索引(待輸入的光標(biāo)位置)
currentIndex = controller.text.length;
//Item寬度(這里判斷如果設(shè)置了寬度并且合理就使用當(dāng)前設(shè)置的寬度,否則平均計算)
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 拓展
}
//繪制文字內(nèi)容
_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);
}
//繪制當(dāng)前位置邊框
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));
//當(dāng)前光標(biāo)樣式
if (currentIndex != -1 && currentIndex == i && focusNode.hasFocus) {
linePaint.color = borderSelectColor;
canvas.drawRRect(rRect, linePaint);
}
//默認(rèn)樣式
else {
linePaint.color = borderColor;
canvas.drawRRect(rRect, linePaint);
}
}
}
/*
* 繪制橫線樣式
*/
void _drawLineCode(Canvas canvas, Size size) {
for (int i = 0; i < maxLength; i++) {
//當(dāng)前選中狀態(tài)
if (controller.value.text.length == i && focusNode.hasFocus) {
linePaint.color = borderSelectColor;
}
//默認(rèn)狀態(tài)
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++) {
//當(dāng)前繪制的圓圈的左x軸坐標(biāo)
double left = 0;
if (i == 0) {
left = space + i * itemWidth;
} else {
left = space + i * itemWidth + itemSpace * i;
}
//圓心坐標(biāo)
double cx = left + itemWidth / 2.0;
double cy = height / 2.0;
//圓形半徑
double radius = itemWidth / 5.0;
//默認(rèn)樣式
if (i >= currentIndex) {
linePaint.style = PaintingStyle.fill;
canvas.drawCircle(Offset(cx, cy), radius, linePaint);
}
}
}
/*
* 繪制驗(yàn)證碼文字
*/
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;
}
}Github:https://github.com/yixiaolunhui/flutter_xy
以上就是Flutter版本的自定義短信驗(yàn)證碼實(shí)現(xiàn)示例解析的詳細(xì)內(nèi)容,更多關(guān)于Flutter 自定義短信驗(yàn)證碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
實(shí)例講解Android中ViewPager組件的一些進(jìn)階使用技巧
這篇文章主要介紹了Android中ViewPager組件的一些進(jìn)階使用技巧,包括添加標(biāo)題與onPagerChangeListener監(jiān)聽使用等,需要的朋友可以參考下2016-03-03
Android中FileProvider的各種場景應(yīng)用詳解
這篇文章主要為大家介紹了Android中FileProvider的各種場景應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Android用PopupWindow實(shí)現(xiàn)自定義Dailog
這篇文章主要為大家詳細(xì)介紹了Android用PopupWindow實(shí)現(xiàn)自定義Dailog的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
詳解Android TextView屬性ellipsize多行失效的解決思路
這篇文章主要介紹了Android TextView屬性ellipsize多行失效的解決思路,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
基于Android實(shí)現(xiàn)滾動頭部懸停效果
這篇文章主要為大家詳細(xì)介紹了如何在?Android?中實(shí)現(xiàn)這種滾動頭部懸停效果,并提供完整源碼,方便學(xué)習(xí)和實(shí)際應(yīng)用,有需要的小伙伴可以了解一下2025-04-04
Okhttp3實(shí)現(xiàn)爬取驗(yàn)證碼及獲取Cookie的示例
本篇文章主要介紹了Okhttp3實(shí)現(xiàn)爬取驗(yàn)證碼及獲取Cookie的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10

