基于Flutter實(shí)現(xiàn)手勢(shì)密碼加密與解鎖功能
前言
密碼的由來(lái):在公元前405年,由古希臘和斯巴達(dá)的戰(zhàn)爭(zhēng)中,由于斯巴達(dá)盟友波斯帝國(guó)背叛,導(dǎo)致古希臘和斯巴達(dá)兩敗俱傷,這時(shí)斯巴達(dá)抓了一個(gè)波斯國(guó)的信使,這個(gè)信使 沒(méi)有任何情報(bào),只有一條有著雜亂無(wú)章的希臘字母的普通腰帶,最終斯巴達(dá)統(tǒng)帥破解了這條腰帶,成功擊敗了希臘。這就是世界上最早的密碼。同時(shí)也是世界上最早的解密。
密碼在我們生活中無(wú)處不在,作為個(gè)人隱私的最后一道防線顯得無(wú)比的重要,現(xiàn)在世界有各種各樣形形色色的密碼,解密方式也是層出不窮,加密,解密的過(guò)程里充滿了數(shù)學(xué)以及計(jì)算機(jī)的知識(shí),而現(xiàn)在手機(jī)的加密的方式也有很多、數(shù)字、手勢(shì)、指紋、人臉、虹膜等等方式多種多樣,有加密就有解密、有破譯等等,有些App為了安全起見(jiàn),例如招商銀行的銀行類的App在每次啟動(dòng)的時(shí)候都需要解鎖驗(yàn)證身份,那么今天我們用Flutter實(shí)現(xiàn)一個(gè)其中的加密方式,手勢(shì)密碼。
知識(shí)點(diǎn):手勢(shì)識(shí)別、繪制、動(dòng)畫(huà)
1、繪制靜態(tài)圖形
手勢(shì)密碼一般都是九宮格的形狀,所以第一步我們先把這個(gè)輔助矩形九宮格畫(huà)出來(lái),然后在每一個(gè)格子內(nèi)就可以繪制我們喜歡的圖形了,九宮格那就很簡(jiǎn)單了。
首先我們定義一個(gè)正方形300*300
為九宮格的區(qū)域,然后對(duì)這個(gè)正方形平均分成9
個(gè)小格子,我們需要找到這9
個(gè)小格子的中心點(diǎn)來(lái)繪制九宮格,那么我們找到之后把每個(gè)小格子的中心用一個(gè)List
存儲(chǔ)起來(lái)方便以后繪制。
代碼:
double size = 300;// 正方形邊長(zhǎng) List<Offset> centerOffset = <Offset>[];// 九宮格中心點(diǎn) // 上面3個(gè) 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個(gè) centerOffset.add(Offset(-size / 3, 0)); centerOffset.add(Offset(-size / 3 + size / 3, 0)); centerOffset.add(Offset(-size / 3 + size / 3 * 2, 0)); // 下面3個(gè) 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));
接下來(lái)我們就可以用這些點(diǎn)繪制九宮格了。
核心代碼:
Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2 ..color = Colors.black87; canvas.drawRect( Rect.fromCenter(center: Offset.zero, width: 300, height: 300), paint); // 繪制輔助區(qū)域 _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); } }
效果圖:
得到九宮格之后,我們看到很多手勢(shì)解鎖小格子內(nèi)都是圓形,這里我們繼續(xù)在每個(gè)小格子繪制圓形圖案,
核心代碼:
// 繪制圓 _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); } }
效果圖:
到這里基本圖形已經(jīng)繪制完了。
2、存儲(chǔ)手勢(shì)密碼數(shù)據(jù)
首先我們看下手勢(shì)解鎖一共有按下、移動(dòng)、抬起三個(gè)動(dòng)作組成,在移動(dòng)的過(guò)程中會(huì)經(jīng)過(guò)九宮格內(nèi)部的圓形區(qū)域,也就是說(shuō),在經(jīng)過(guò)圓形區(qū)域的時(shí)候,我們需要將這個(gè)數(shù)據(jù)保存下來(lái)然后通知畫(huà)布進(jìn)行更新,這里的邏輯跟我之前一篇繪制海豚那篇原理一樣,首先創(chuàng)建UnlockController
類繼承ChangeNotifier
。這里我們將九宮格中的每一個(gè)小格子對(duì)應(yīng)一個(gè)數(shù)字進(jìn)行封裝一下。也是之后的設(shè)置和解鎖需要保存的數(shù)據(jù)。 這樣的話我們上面的九宮格就需要修改一下了,將Offset改為PassWord就行。
數(shù)據(jù)代碼:
class UnlockController extends ChangeNotifier { // 存儲(chǔ)按壓的點(diǎn)集合 List<PassWord> _points = []; List<PassWord> get points => _points; // 當(dāng)前手指的位置 Offset? _currentOffset; Offset? get currentOffset => _currentOffset; // set currentOffset(Offset? value) { _currentOffset = value; notifyListeners(); } addPoint(PassWord offset) { _points.add(offset); notifyListeners(); } // 清除所有點(diǎn) clearAllPoint() { _points.clear(); notifyListeners(); } } class PassWord { int num; // 密碼數(shù)字 Offset offset; // 密碼數(shù)字對(duì)應(yīng)的點(diǎn) PassWord(this.num, this.offset); }
有了這些數(shù)據(jù)之后我們接下來(lái)就要跟手勢(shì)進(jìn)行交互了。
3、添加手勢(shì)交互
接著上面剛說(shuō)的,交互一共有三種狀態(tài),按下、移動(dòng)、抬起,那么我們就先對(duì)這三種狀態(tài)進(jìn)行監(jiān)聽(tīng),
GestureDetector( child: CustomPaint( size: Size(size, size), painter: _GesturesUnlockPainter(_unlockController, centerOffset), ), onPanDown: (d) { // 手指按下 }, onPanUpdate: (d) { // 移動(dòng) }, onPanEnd: (d) { // 抬起結(jié)束 }, )
交互思路: 手勢(shì)密碼的特點(diǎn)是每個(gè)九宮格只允許點(diǎn)亮一次,再次經(jīng)過(guò)不保存數(shù)據(jù)。
按下: 判斷是否在九宮格內(nèi)部任一圓形區(qū)域內(nèi),在:點(diǎn)亮圓形,保存數(shù)據(jù),不在:不做任何操作,
移動(dòng): 判斷是否移動(dòng)到九宮格任一圓形區(qū)域內(nèi),在:判斷是否已點(diǎn)亮-未點(diǎn)亮:點(diǎn)亮圓形,保存數(shù)據(jù),已點(diǎn)亮:不做任何操作,不在區(qū)域內(nèi),也不做數(shù)據(jù)任何操作。
抬起: 獲取密碼,進(jìn)行設(shè)置或驗(yàn)證。
大概思路還是比較清晰的,下面我們就對(duì)這些判斷條件進(jìn)行判斷。
代碼:
///手指按下、移動(dòng)觸發(fā) void judgeZone(Offset src) { /// 循環(huán)所有的九宮格 for (int i = 0; i < centerOffset.length; i++) { var srcTranslate = src.translate(-size / 2, -size / 2); // 判斷手指按的位置是否在九宮格圓形區(qū)域 if (judgeCircleArea(srcTranslate, centerOffset[i].offset, 30)) { // 在 判斷是否已添加 for (int j = 0; j < _unlockController.points.length; j++) { if (_unlockController.points[j] == centerOffset[i]) { // 已添加過(guò) 返回 return; } } // 未添加過(guò) 進(jìn)行添加 _unlockController.addPoint(centerOffset[i]); return; } } // 無(wú)點(diǎn) } ///判斷出是否在某點(diǎn)的半徑為r圓范圍內(nèi) bool judgeCircleArea(Offset src, Offset dst, double r)=>(src - dst).distance <= r;
有了手指數(shù)據(jù)之后,我們就能根據(jù)這些數(shù)據(jù)的變化進(jìn)而通知畫(huà)布進(jìn)行更新了。
4、繪制、刷新密碼線
上面我們已經(jīng)拿到數(shù)據(jù)了,接下來(lái)我們進(jìn)行繪制密碼線,如何刷新畫(huà)布之前的文章我已經(jīng)講過(guò)很多遍了,這里不熟悉的同學(xué)可以看下之前繪制小海豚那一篇文章貝塞爾曲線繪制一個(gè)小海豚,回歸正題,既然我們拿到了手指經(jīng)過(guò)的九宮格的中心點(diǎn),那繪制線就變得非常容易了,
核心代碼:
var offsets = unlockController.points.map((e) => e.offset).toList(); // 繪制按壓點(diǎn) 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) { // 繪制當(dāng)前手勢(shì)線 未經(jīng)過(guò)九宮格圓形時(shí) 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);
這樣我們就把手勢(shì)經(jīng)過(guò)的密碼線繪制出來(lái)了。
效果圖:
5、加入密碼錯(cuò)誤動(dòng)畫(huà)
當(dāng)我們輸入密碼錯(cuò)誤時(shí),給用戶一個(gè)提示密碼錯(cuò)誤的交互也是很有必要的,那么接下來(lái)我們就添加一個(gè)簡(jiǎn)單文字抖動(dòng)效果提示用戶密碼輸入錯(cuò)誤。
添加動(dòng)畫(huà)也很簡(jiǎn)單,思路: 不斷改變文字的邊距從而達(dá)到抖動(dòng)效果。
核心代碼:
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(); // 獲取這個(gè)數(shù)字的小數(shù)部分 double? y; if (d <= 0.5) { y = 2 * d; } else { y = 1 - 2 * (d - 0.5); } return y; }
假設(shè)我們?cè)O(shè)置的密碼是14789
,也就是L
型,看下最終效果:
完整源碼之后我會(huì)放到github上。
總結(jié)
通過(guò)手勢(shì)識(shí)別我們用到了App中核心的三大框架,手勢(shì)、繪制、以及動(dòng)畫(huà)中的知識(shí)。其實(shí)原理也并不復(fù)雜,只要掌握了這三大框架的核心基礎(chǔ)知識(shí)
到此這篇關(guān)于基于Flutter實(shí)現(xiàn)手勢(shì)密碼加密與解鎖功能的文章就介紹到這了,更多相關(guān)Flutter手勢(shì)密碼加密解鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android實(shí)現(xiàn)倒計(jì)時(shí)功能代碼
實(shí)現(xiàn)倒計(jì)時(shí)每隔1秒,變換一下時(shí)間,截圖如下,感興趣的朋友想看下實(shí)現(xiàn)代碼,希望對(duì)你學(xué)習(xí)有所幫助2013-06-06Android TimerTask 的簡(jiǎn)單應(yīng)用及注意事項(xiàng)
這篇文章主要介紹了Android TimerTask 的簡(jiǎn)單應(yīng)用及注意事項(xiàng)的相關(guān)資料,需要的朋友可以參考下2017-06-06詳解Android 基于TCP和UDP協(xié)議的Socket通信
這篇文章主要介紹了詳解Android 基于TCP和UDP協(xié)議的Socket通信,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11詳解關(guān)于Android Studio中安裝和gradle的一些坑
本篇文章主要介紹了關(guān)于Android Studio中安裝和gradle的一些坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10Android開(kāi)發(fā)之BroadcastReceiver用法實(shí)例分析
這篇文章主要介紹了Android開(kāi)發(fā)之BroadcastReceiver用法,實(shí)例分析了Android中廣播的相關(guān)使用技巧,需要的朋友可以參考下2015-05-05