效果图

自定义手势代码
import 'package:flutter/material.dart'
/// 手势绘制结果
enum GesturePwdResult {
/// 手势正确
success,
/// 手势错误
failed,
/// 点位数不满足
lack,
}
/// 手势组件
class GesturePassword extends StatefulWidget {
const GesturePassword({
super.key,
required this.onChange,
required this.savePwd,
this.size,
this.autoRestore,
this.duration,
})
/// 手势区域大小
final Size? size
/// 保存的手势密码
final List<int> savePwd
/// 回调选中的点
final Function(GesturePwdResult result) onChange
/// 错误时是否需要自动消除,默认自动消除
final bool? autoRestore
/// 消除延时时间,默认时间是100毫秒
final Duration? duration
@override
State<GesturePassword> createState() => _GesturePasswordState()
}
class _GesturePasswordState extends State<GesturePassword> {
/// 触摸区域
late Size _size
/// 手势点列表
List<Offset> points = []
/// 点位半径
double pointRadius = 10
/// 点位选中时的外环半径
late double pointRingRadius
/// 点外围可触发选中的半径
late double touchRadius
/// 记录当前选中的点的索引
List<int> selectedPoints = []
/// 记录当前触摸点的位置
Offset currentTouchPoint = Offset.zero
/// 选中时点颜色
final Color pointColor = const Color.fromRGBO(15, 120, 205, 1)
/// 选中时光环颜色
final Color ringColor = const Color.fromRGBO(231, 241, 250, 1)
/// 错误时选中点的颜色
final Color errorPointColor = const Color.fromRGBO(250, 81, 81, 1)
/// 错误时选中光环的颜色
final Color errorRingColor = const Color.fromRGBO(254, 237, 237, 1)
/// 点的颜色
late Color pointSelectColor
/// 光环颜色
late Color ringSelectColor
@override
void initState() {
super.initState()
// 设置手势操作范围
_size = widget.size ?? const Size.square(200)
// 设置选中点的颜色
_drawColor()
// 计算九个点的位置
initPoints()
}
/// 生成九宫格点位
void initPoints() {
//计算一个格子的半径
double singleRadius = _size.width / 6
for (int i = 0
for (int j = 0
double x = singleRadius + j * (2 * singleRadius)
double y = singleRadius + i * (2 * singleRadius)
points.add(Offset(x, y))
}
}
// 计算可触发选中范围
touchRadius = singleRadius*2/3
// 计算选中的圆环半径
pointRingRadius = singleRadius / 2
// 计算点的半径
pointRadius = pointRingRadius / 2.5
}
int? _getTouchIndex(Offset position){
for (int i = 0
if ((position - points[i]).distance < touchRadius) {
return i
}
}
return null
}
/// 手势开始事件处理
void _handStart(DragStartDetails details) {
setState(() {
_drawColor()
selectedPoints.clear()
// 检查触摸点是否在某个解锁点内
int? touchIndex = _getTouchIndex(details.localPosition)
if(touchIndex!=null){
selectedPoints.add(touchIndex)
}
})
}
/// 手势移动事件处理
void _handMove(DragUpdateDetails details) {
setState(() {
_drawColor()
currentTouchPoint = details.localPosition
int? touchIndex = _getTouchIndex(currentTouchPoint)
if(touchIndex!=null && !selectedPoints.contains(touchIndex)){
// 判断两点间有没有别的点
_addIntermediateDots(touchIndex)
selectedPoints.add(touchIndex)
}
})
}
/// 手势抬起事件处理
void _handEnd(DragEndDetails details) {
setState(() {
currentTouchPoint = Offset.zero
// 判断点位是否小于4个
if (selectedPoints.length < 4) {
_errorError()
_delayRestore()
widget.onChange(GesturePwdResult.lack)
return
}
// 验证解锁结果
String selectPwd = selectedPoints.join()
String correctPwd = widget.savePwd.join()
if (selectPwd == correctPwd) {
_drawColor()
widget.onChange(GesturePwdResult.success)
} else {
_errorError()
_delayRestore()
widget.onChange(GesturePwdResult.failed)
}
})
}
/// 延迟消失
void _delayRestore() {
if ((widget.autoRestore ?? true) == false) return
Duration duration = widget.duration ?? const Duration(milliseconds:100)
Future.delayed(duration, () {
setState(() {
_drawColor()
selectedPoints.clear()
})
})
}
/// 判断两点之间是否有点,选中它
void _addIntermediateDots(int newDot) {
if (selectedPoints.isNotEmpty) {
int lastDot = selectedPoints.last
int midDot = _getMidDot(lastDot, newDot)
if (midDot != -1 && !selectedPoints.contains(midDot)) {
selectedPoints.add(midDot)
}
}
}
int _getMidDot(int dot1, int dot2) {
// 确保 dot1 小于 dot2,方便统一处理
if (dot1 > dot2) {
int temp = dot1
dot1 = dot2
dot2 = temp
}
// 横向判断
if ((dot1 % 3 == 0 && dot2 % 3 == 2) && (dot1 ~/ 3 == dot2 ~/ 3)) {
return dot1 + 1
}
// 纵向判断
if ((dot1 < 3 && dot2 >= 6) && (dot1 % 3 == dot2 % 3)) {
return dot1 + 3
}
// 对角线判断(左上到右下)
if (dot1 == 0 && dot2 == 8) {
return 4
}
// 对角线判断(右上到左下)
if (dot1 == 2 && dot2 == 6) {
return 4
}
return -1
}
/// 蓝色选中
void _drawColor() {
pointSelectColor = pointColor
ringSelectColor = ringColor
}
/// 红色选中
void _errorError() {
pointSelectColor = errorPointColor
ringSelectColor = errorRingColor
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: _handStart,
onPanUpdate: _handMove,
onPanEnd: _handEnd,
child: CustomPaint(
painter: _GesturePainter(
points: points,
selectPoints: selectedPoints,
curTouchPoint: currentTouchPoint,
pointRadius: pointRadius,
pointRingRadius: pointRingRadius,
selectPointColor: pointSelectColor,
selectRingColor: ringSelectColor,
),
size: _size,
),
)
}
}
/// 自定义手势绘制
class _GesturePainter extends CustomPainter {
final List<Offset> points
late List<int> selectPoints
final Offset curTouchPoint
final double pointRadius
final double pointRingRadius
/// 选中时点的颜色
final Color selectPointColor
/// 选中时外环的颜色
final Color selectRingColor
/// 未选中时的颜色
final Color normalColor = const Color.fromRGBO(87, 107, 149, 0.2)
/// 连线宽度
final double lineWidth = 1.5
_GesturePainter({
required this.points,
required this.selectPoints,
required this.curTouchPoint,
required this.pointRadius,
required this.pointRingRadius,
required this.selectPointColor,
required this.selectRingColor,
})
@override
void paint(Canvas canvas, Size size) {
// 绘制九宫格点位
Paint pointPaint = Paint()
..color = normalColor
..style = PaintingStyle.fill
..strokeWidth = 2
for (Offset point in points) {
canvas.drawCircle(point, pointRadius, pointPaint)
}
// 绘制选中点
Paint selectedPointPaint = Paint()..style = PaintingStyle.fill
for (int index in selectPoints) {
//绘制外环
selectedPointPaint.color = selectRingColor
canvas.drawCircle(points[index], pointRingRadius, selectedPointPaint)
//绘制点
selectedPointPaint.color = selectPointColor
canvas.drawCircle(points[index], pointRadius, selectedPointPaint)
}
// 绘制选中点之间的连线
if (selectPoints.isNotEmpty) {
Paint linePaint = Paint()
..color = selectPointColor
..style = PaintingStyle.stroke
..strokeWidth = lineWidth
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round
for (int i = 0
canvas.drawLine(
points[selectPoints[i]], points[selectPoints[i + 1]], linePaint)
}
if (curTouchPoint != Offset.zero) {
canvas.drawLine(points[selectPoints.last], curTouchPoint, linePaint)
}
}
}
@override
bool shouldRepaint(_GesturePainter oldDelegate) {
// return oldDelegate.points != points ||
// oldDelegate.selectPoints != selectPoints ||
// oldDelegate.curTouchPoint != curTouchPoint ||
// oldDelegate.selectPointColor != selectPointColor ||
// oldDelegate.selectRingColor != selectRingColor
return true
}
}
使用
import 'package:flutter/material.dart';
import 'package:gesture_demo/gesture_password.dart';
class TestGesture extends StatefulWidget {
const TestGesture({super.key});
@override
State<TestGesture> createState() => _TestGestureState();
}
class _TestGestureState extends State<TestGesture> {
List<int> savePwd = [0, 1, 2, 4, 6];
final ValueNotifier<String> _tips = ValueNotifier('请绘制手势密码,至少连接4个点');
Color _textColor = Colors.black;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('手势密码')),
body: Column(
children: [
Container(
height: 80,
),
ValueListenableBuilder(
valueListenable: _tips,
builder: (context,value,child) {
return Text(value,
style: TextStyle(fontSize: 14, color: _textColor));
}
),
Expanded(
child: Container(
alignment: Alignment.topCenter,
margin: const EdgeInsets.only(top: 60),
child: Container(
width: 300,
height: 300,
child: GesturePassword(
size: const Size.square(300),
savePwd: savePwd,
onChange: (result) {
switch(result){
case GesturePwdResult.lack:
_tips.value = '至少连接4个点,请重新绘制';
_textColor = Colors.red;
return;
case GesturePwdResult.failed:
_tips.value = '手势不一致,请重新绘制';
_textColor = Colors.red;
return;
default:
_tips.value = '验证成功';
_textColor = Colors.blue;
}
},
),
)
)),
],
));
}
}