效果图(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多平台发布