Flutter 滑動(dòng)嗡髓、拖動(dòng)驗(yàn)證 人機(jī)識(shí)別 界面實(shí)現(xiàn)

覺(jué)得這種交互很有意思,相對(duì)其他驗(yàn)證 比較符合用戶(hù)體驗(yàn)烹笔。

只實(shí)現(xiàn)了前端的動(dòng)作邏輯,以及驗(yàn)證是否拖動(dòng)到正確區(qū)域抛丽。

未達(dá)到真正的人機(jī)識(shí)別

邏輯也很簡(jiǎn)單

1:隨機(jī)生成 滑塊按鈕谤职、答案區(qū)域 的位置。

2:拖動(dòng)的過(guò)程中驗(yàn)證是否在答案區(qū)域亿鲜,如果在 答案區(qū)域變成綠色允蜈。

3:拖動(dòng)結(jié)束,驗(yàn)證是否在答案區(qū)域蒿柳;在:返回成功 不在:執(zhí)行滑塊歸位動(dòng)畫(huà)饶套。

(拼圖滑塊驗(yàn)證碼)

以下是github地址,覺(jué)得對(duì)你有幫助 請(qǐng)不要吝嗇star ~

Github




主要的代碼也就100多行(格式不知道為啥不對(duì)齊)

import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shack/widget/dotted_border/r_dotted_line_border.dart'; /// 驗(yàn)證widget /// return /// [true] 成功 /// [false] 失敗 class DemoVerity extends StatefulWidget { final Function lister; DemoVerity({required this.lister}); @override _DemoVerityState createState() => _DemoVerityState(); } class _DemoVerityState extends State<DemoVerity> with TickerProviderStateMixin { /// 半徑 final double radius = 32.0; /// 拖動(dòng)控制 /// 左上角點(diǎn)坐標(biāo)垒探,布局時(shí)會(huì)自動(dòng)轉(zhuǎn)換為中心點(diǎn) /// 初始值 Offset offsetCtrInit = Offset.zero; /// 拖動(dòng)控制 /// 左上角點(diǎn)坐標(biāo)妓蛮,布局時(shí)會(huì)自動(dòng)轉(zhuǎn)換為中心點(diǎn) /// 拖動(dòng)會(huì)變化 Offset offsetCtr = Offset.zero; /// 正確區(qū)域 中心點(diǎn) Offset offsetAwe = Offset.zero; late AnimationController anwerAnimationController; late AnimationController animationController; /// 錯(cuò)誤歸位動(dòng)畫(huà) late final Animation<double> moveAnimation; /// 答案區(qū)域縮放動(dòng)畫(huà) late final Animation<double> scaleAnimation; /// 兩點(diǎn)距離 double get distance => (offsetAwe - offsetCtr).distance; /// 是否滑入正確區(qū)域 bool success = false; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 200)); animationController.addStatusListener((status) { if (status == AnimationStatus.completed) { animationController.reset(); setState(() { offsetCtr = offsetCtrInit; }); } }); moveAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: animationController, curve: const Cubic(0.68, 0, 0, 1.5), ), ); anwerAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800)); scaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate( CurvedAnimation( parent: anwerAnimationController, curve: Curves.fastOutSlowIn, ), ); WidgetsBinding.instance!.addPostFrameCallback((timeStamp) { final size = context.size ?? Size.zero; final x1 = radius + Random().nextInt((size.width - radius * 2.2).toInt()); final y1 = radius + Random().nextInt(30); /// 正確答案在下半部分區(qū)域 final x2 = radius + Random().nextInt((size.width - radius * 2.6).toInt()); final y2 = size.height * 0.7 + Random().nextInt((size.height * 0.3).toInt()) - radius * 1.2 - MediaQuery.of(context).padding.bottom; setState(() { offsetCtr = Offset(x1, y1); offsetCtrInit = offsetCtr; offsetAwe = Offset(x2, y2); }); anwerAnimationController.repeat(reverse: true); }); } @override void dispose() { animationController.dispose(); anwerAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, child: Stack( clipBehavior: Clip.none, children: [ /// 答案區(qū)域 Positioned( top: offsetAwe.dy - radius, left: offsetAwe.dx - radius, child: ScaleTransition( scale: scaleAnimation, child: Container( width: radius * 2, height: radius * 2, alignment: const Alignment(0, 0), decoration: BoxDecoration( border: RDottedLineBorder.all( width: 1, color: success ? Colors.green : Colors.blue), color: success ? const Color(0xffe9faef) : const Color(0xffe9f5fe), shape: BoxShape.circle, ), child: success ? const SizedBox() : const Icon(Icons.add, size: 20, color: Colors.blue), ), ), ), /// 滑塊 AnimatedBuilder( animation: animationController, builder: (_, child) { return Positioned( left: offsetCtr.dx - ((offsetCtr.dx - offsetCtrInit.dx) * moveAnimation.value) - radius, top: offsetCtr.dy - ((offsetCtr.dy - offsetCtrInit.dy) * moveAnimation.value) - radius, child: child!, ); }, child: GestureDetector( onPanUpdate: (DragUpdateDetails details) { /// 答案半徑 * 0.9 final rDistance = radius * 0.9; /// 答案區(qū) 內(nèi) if ((distance < rDistance) && !success) { success = true; // 震動(dòng) HapticFeedback.mediumImpact(); anwerAnimationController.stop(); anwerAnimationController.animateTo(1.0, duration: const Duration()); } /// 答案區(qū) 外 if ((distance >= rDistance) && success) { success = false; if (!anwerAnimationController.isAnimating) anwerAnimationController.repeat(reverse: true); } if (!mounted) return; setState(() { offsetCtr += Offset(details.delta.dx, details.delta.dy); }); }, onPanEnd: (DragEndDetails details) { // debugPrint('longer 結(jié)束拖動(dòng) 是否答案內(nèi) >>> $success '); // 如果沒(méi)有在答案內(nèi) 執(zhí)行返回位置的動(dòng)畫(huà) if (!success) { animationController.forward(); } widget.lister(success); }, child: Container( width: radius * 2, height: radius * 2, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xff016df3), shape: BoxShape.circle, boxShadow: const <BoxShadow>[ BoxShadow( color: Color(0xFF616161), offset: Offset(4.0, 4.0), blurRadius: 8.0, ), ], ), child: Image.asset('assets/img/safe_icon.jpg'), ), ), ), ], ), ); } }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市圾叼,隨后出現(xiàn)的幾起案子蛤克,更是在濱河造成了極大的恐慌扔仓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咖耘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡撬码,警方通過(guò)查閱死者的電腦和手機(jī)儿倒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呜笑,“玉大人夫否,你說(shuō)我怎么就攤上這事〗行玻” “怎么了凰慈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)驼鹅。 經(jīng)常有香客問(wèn)我微谓,道長(zhǎng),這世上最難降的妖魔是什么输钩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任豺型,我火速辦了婚禮,結(jié)果婚禮上买乃,老公的妹妹穿的比我還像新娘姻氨。我一直安慰自己,他們只是感情好剪验,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布肴焊。 她就那樣靜靜地躺著,像睡著了一般功戚。 火紅的嫁衣襯著肌膚如雪娶眷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天疫铜,我揣著相機(jī)與錄音茂浮,去河邊找鬼。 笑死壳咕,一個(gè)胖子當(dāng)著我的面吹牛席揽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谓厘,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼幌羞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了竟稳?” 一聲冷哼從身側(cè)響起属桦,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熊痴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后聂宾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體果善,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年系谐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巾陕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纪他,死狀恐怖鄙煤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茶袒,我是刑警寧澤梯刚,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站薪寓,受9級(jí)特大地震影響亡资,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜向叉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一沟于、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧植康,春花似錦旷太、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至冻记,卻和暖如春睡毒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冗栗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工演顾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隅居。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓钠至,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親胎源。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棉钧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容