目录
前言
密码的由来:在公元前405年,由古希腊和斯巴达的战争中,由于斯巴达盟友波斯帝国背叛,导致古希腊和斯巴达两败俱伤,这时斯巴达抓了一个波斯国的信使,这个信使 没有任何情报,只有一条有着杂乱无章的希腊字母的普通腰带,最终斯巴达统帅破解了这条腰带,成功击败了希腊。这就是世界上最早的密码。同时也是世界上最早的解密。
密码在我们生活中无处不在,作为个人隐私的最后一道防线显得无比的重要,现在世界有各种各样形形色色的密码,解密方式也是层出不穷,加密,解密的过程里充满了数学以及计算机的知识,而现在手机的加密的方式也有很多、数字、手势、指纹、人脸、虹膜等等方式多种多样,有加密就有解密、有破译等等,有些App为了安全起见,例如招商银行的银行类的App在每次启动的时候都需要解锁验证身份,那么今天我们用Flutter实现一个其中的加密方式,手势密码。
知识点:手势识别、绘制、动画
1、绘制静态图形
手势密码一般都是九宫格的形状,所以第一步我们先把这个辅助矩形九宫格画出来,然后在每一个格子内就可以绘制我们喜欢的图形了,九宫格那就很简单了。
首先我们定义一个正方形300*300
为九宫格的区域,然后对这个正方形平均分成9
个小格子,我们需要找到这9
个小格子的中心点来绘制九宫格,那么我们找到之后把每个小格子的中心用一个List
存储起来方便以后绘制。
代码:
double size = 300;// 正方形边长 List<Offset> centerOffset = <Offset>[];// 九宫格中心点 // 上面3个 centerOffset.add(Offset(-size / 3, -size / 3)); centerOffset.add(Offset(-size / 3 + size / 3, -size / 3)); centerOffset.add(Offset(-size / 3 + size / 3 * 2, -size / 3)); // 中间3个 centerOffset.add(Offset(-size / 3, 0)); centerOffset.add(Offset(-size / 3 + size / 3, 0)); centerOffset.add(Offset(-size / 3 + size / 3 * 2, 0)); // 下面3个 centerOffset.add(Offset(-size / 3, size / 3)); centerOffset.add(Offset(-size / 3 + size / 3, size / 3)); centerOffset.add(Offset(-size / 3 + size / 3 * 2, size / 3));
接下来我们就可以用这些点绘制九宫格了。
核心代码:
Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2 ..color = Colors.black87; canvas.drawRect( Rect.fromCenter(center: Offset.zero, width: 300, height: 300), paint); // 绘制辅助区域 _drawHelpRect(canvas, size, paint); void _drawHelpRect(Canvas canvas, Size size, Paint paint) { for (int i = 0; i < centerOffset.length; i++) { canvas.drawRect( Rect.fromCenter(center: centerOffset[i], width: 100, height: 100), paint); } }
效果图:
得到九宫格之后,我们看到很多手势解锁小格子内都是圆形,这里我们继续在每个小格子绘制圆形图案,
核心代码:
// 绘制圆 _drawCirCle(canvas, size, paint); void _drawCirCle(Canvas canvas, Size size, Paint paint) { for (int i = 0; i < centerOffset.length; i++) { canvas.drawCircle(centerOffset[i], 30, paint..color = Colors.black87); } }
效果图:
到这里基本图形已经绘制完了。
2、存储手势密码数据
首先我们看下手势解锁一共有按下、移动、抬起三个动作组成,在移动的过程中会经过九宫格内部的圆形区域,也就是说,在经过圆形区域的时候,我们需要将这个数据保存下来然后通知画布进行更新,这里的逻辑跟我之前一篇绘制海豚那篇原理一样,首先创建UnlockController
类继承ChangeNotifier
。这里我们将九宫格中的每一个小格子对应一个数字进行封装一下。也是之后的设置和解锁需要保存的数据。 这样的话我们上面的九宫格就需要修改一下了,将Offset改为PassWord就行。
数据代码:
class UnlockController extends ChangeNotifier { // 存储按压的点集合 List<PassWord> _points = []; List<PassWord> get points => _points; // 当前手指的位置 Offset? _currentOffset; Offset? get currentOffset => _currentOffset; // set currentOffset(Offset? value) { _currentOffset = value; notifyListeners(); } addPoint(PassWord offset) { _points.add(offset); notifyListeners(); } // 清除所有点 clearAllPoint() { _points.clear(); notifyListeners(); } } class PassWord { int num; // 密码数字 Offset offset; // 密码数字对应的点 PassWord(this.num, this.offset); }
有了这些数据之后我们接下来就要跟手势进行交互了。
3、添加手势交互
接着上面刚说的,交互一共有三种状态,按下、移动、抬起,那么我们就先对这三种状态进行监听,
GestureDetector( child: CustomPaint( size: Size(size, size), painter: _GesturesUnlockPainter(_unlockController, centerOffset), ), onPanDown: (d) { // 手指按下 }, onPanUpdate: (d) { // 移动 }, onPanEnd: (d) { // 抬起结束 }, )
交互思路: 手势密码的特点是每个九宫格只允许点亮一次,再次经过不保存数据。
按下: 判断是否在九宫格内部任一圆形区域内,在:点亮圆形,保存数据,不在:不做任何操作,
移动: 判断是否移动到九宫格任一圆形区域内,在:判断是否已点亮-未点亮:点亮圆形,保存数据,已点亮:不做任何操作,不在区域内,也不做数据任何操作。
抬起: 获取密码,进行设置或验证。
大概思路还是比较清晰的,下面我们就对这些判断条件进行判断。
代码:
///手指按下、移动触发 void judgeZone(Offset src) { /// 循环所有的九宫格 for (int i = 0; i < centerOffset.length; i++) { var srcTranslate = src.translate(-size / 2, -size / 2); // 判断手指按的位置是否在九宫格圆形区域 if (judgeCircleArea(srcTranslate, centerOffset[i].offset, 30)) { // 在 判断是否已添加 for (int j = 0; j < _unlockController.points.length; j++) { if (_unlockController.points[j] == centerOffset[i]) { // 已添加过 返回 return; } } // 未添加过 进行添加 _unlockController.addPoint(centerOffset[i]); return; } } // 无点 } ///判断出是否在某点的半径为r圆范围内 bool judgeCircleArea(Offset src, Offset dst, double r)=>(src - dst).distance <= r;
有了手指数据之后,我们就能根据这些数据的变化进而通知画布进行更新了。
4、绘制、刷新密码线
上面我们已经拿到数据了,接下来我们进行绘制密码线,如何刷新画布之前的文章我已经讲过很多遍了,这里不熟悉的同学可以看下之前绘制小海豚那一篇文章贝塞尔曲线绘制一个小海豚,回归正题,既然我们拿到了手指经过的九宫格的中心点,那绘制线就变得非常容易了,
核心代码:
var offsets = unlockController.points.map((e) => e.offset).toList(); // 绘制按压点 canvas.drawPoints( PointMode.points, offsets, paint ..strokeWidth = 20 ..strokeCap = StrokeCap.round ..color = Colors.red); // 绘制密码 Path path = Path(); if (unlockController.points.isNotEmpty) { path.moveTo(unlockController.points[0].offset.dx, unlockController.points[0].offset.dy); if (unlockController.currentOffset != null) { // 绘制当前手势线 未经过九宫格圆形时 canvas.drawLine(unlockController.points.last.offset, unlockController.currentOffset!, paint..strokeWidth = 2); } } for (int i = 1; i < unlockController.points.length; i++) { path.lineTo(unlockController.points[i].offset.dx, unlockController.points[i].offset.dy); } canvas.drawPath(path, paint..strokeWidth = 2);
这样我们就把手势经过的密码线绘制出来了。
效果图:
5、加入密码错误动画
当我们输入密码错误时,给用户一个提示密码错误的交互也是很有必要的,那么接下来我们就添加一个简单文字抖动效果提示用户密码输入错误。
添加动画也很简单,思路: 不断改变文字的边距从而达到抖动效果。
核心代码:
late AnimationController _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500)); late CurvedAnimation curvedAnimation = CurvedAnimation(curve: Curves.easeIn, parent: _animationController); late Animation<double> animation = Tween(begin: 0.0, end: 10.0).animate(curvedAnimation) ..addStatusListener((status) { if (status == AnimationStatus.completed) { _animationController.reset(); } }); AnimatedBuilder( animation: animation, builder: (ctx, child) { return Container( margin: EdgeInsetsDirectional.only( bottom: 20, start: _errorPwd() * 20, end: animation.value), child: Text( text, style: TextStyle(fontSize: 20, color: textColor), ), ); }), double _errorPwd() { double x = animation.value; // 变化速度 0-10, double d = x - x.truncate(); // 获取这个数字的小数部分 double? y; if (d <= 0.5) { y = 2 * d; } else { y = 1 - 2 * (d - 0.5); } return y; }
假设我们设置的密码是14789
,也就是L
型,看下最终效果:
完整源码之后我会放到github上。
总结
通过手势识别我们用到了App中核心的三大框架,手势、绘制、以及动画中的知识。其实原理也并不复杂,只要掌握了这三大框架的核心基础知识