国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件

這篇具有很好參考價(jià)值的文章主要介紹了Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

Flutter筆記
手寫(xiě)一個(gè)人機(jī)滑塊驗(yàn)證碼

作者李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
郵箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/133529459


寫(xiě) Flutter 項(xiàng)目時(shí),遇到需要滑塊驗(yàn)證碼功能?;瑝K驗(yàn)證碼屬于人機(jī)驗(yàn)證碼的一種,看起來(lái)像是在一個(gè)圖片中“挖去”了一塊,然后通過(guò)用戶手動(dòng)操作滑塊,讓被“挖去”的部分移回來(lái)。由于我不想使用各種第三方模塊,因此決定自己實(shí)現(xiàn)一個(gè)初版以后慢慢添磚加瓦。本文是對(duì)第一個(gè)版本的一點(diǎn)記錄。

Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件,Dart語(yǔ)言與Flutter框架開(kāi)發(fā)筆記,前端、桌面端、移動(dòng)端、UI、構(gòu)建工具,Flutter,Dart,驗(yàn)證碼,滑塊驗(yàn)證,插件


1. 概述

1.1 關(guān)于本文

1.2 什么是人機(jī)驗(yàn)證碼

概念

Flutter 開(kāi)發(fā)中,使用人機(jī)驗(yàn)證碼(也稱為 CAPTCHA,即 Completely Automated Public Turing test to tell Computers and Humans Apart)通常是為了增強(qiáng)應(yīng)用程序的安全性和防止惡意活動(dòng)。

目的

  1. 防止自動(dòng)化攻擊:惡意用戶和自動(dòng)化腳本可以嘗試大規(guī)模攻擊應(yīng)用程序,例如注冊(cè)多個(gè)虛假帳戶、暴力破解密碼、濫發(fā)垃圾郵件或提交虛假表單。人機(jī)驗(yàn)證碼可以幫助阻止這些自動(dòng)化攻擊,因?yàn)樗鼈円笥脩糇C明自己是真人而不是機(jī)器;

  2. 防止垃圾數(shù)據(jù)輸入:人機(jī)驗(yàn)證碼可以確保用戶提交的數(shù)據(jù)是有效和真實(shí)的。例如,在用戶注冊(cè)過(guò)程中,驗(yàn)證碼可以防止惡意用戶自動(dòng)化注冊(cè)虛假帳戶,從而保護(hù)應(yīng)用程序的數(shù)據(jù)質(zhì)量;

  3. 防止濫用資源:如果應(yīng)用程序提供某種資源,如 API 訪問(wèn)或文件下載,希望防止單個(gè)用戶或惡意機(jī)器人濫用這些資源。通過(guò)要求用戶在訪問(wèn)這些資源之前進(jìn)行驗(yàn)證碼驗(yàn)證,可以限制濫用的可能性;

  4. 增加安全性:在某些情況下,用戶可能需要進(jìn)行敏感操作,如更改密碼、恢復(fù)帳戶或進(jìn)行金融交易。在這些情況下,驗(yàn)證碼可以提供額外的安全性,確保只有授權(quán)用戶可以執(zhí)行這些操作;

Flutter 中實(shí)施人機(jī)驗(yàn)證碼通常涉及使用插件或集成第三方服務(wù),這些服務(wù)提供了生成和驗(yàn)證驗(yàn)證碼的功能。

總之,人機(jī)驗(yàn)證碼是一種重要的安全措施,可幫助保護(hù) Flutter 應(yīng)用程序免受各種惡意活動(dòng)的威脅,并提高用戶數(shù)據(jù)的質(zhì)量和應(yīng)用程序的整體安全性。

滑動(dòng)驗(yàn)證碼

1.3 項(xiàng)目地址

  1. flutter pub: https://pub.dev/packages/jc_captcha
  2. git lab: http://thispage.tech:9680/jclee1995/flutter-jc-captcha

2. 先看使用方法

2.1 安裝

flutter pub add jc_captcha

2.2 編碼

import 'package:flutter/material.dart';
import 'package:jc_captcha/jc_captcha.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Captcha Plugin Example'),
        ),
        body: CaptchaWidget(
          imageUrl:
              'http://thispage.tech:9680/jclee1995/flutter-jc-captcha/-/raw/master/example/test_picture.png',
          onSuccess: () {
            print('驗(yàn)證成功');
          },
          onFail: () {
            print('驗(yàn)證失敗');
          },
        ),
      ),
    );
  }
}

說(shuō)明:

驗(yàn)證碼僅驗(yàn)證一次即失效:

驗(yàn)證碼有兩個(gè)狀態(tài),且狀態(tài)該轉(zhuǎn)換是一次性的,即:

未驗(yàn)證 =》 已驗(yàn)證
  • 從未驗(yàn)證到已驗(yàn)證僅僅轉(zhuǎn)換一次,轉(zhuǎn)換結(jié)果有驗(yàn)證成功和驗(yàn)證失?。?/li>
  • 如果驗(yàn)證成功,則執(zhí)行成功回調(diào) onSuccess;
  • 如果驗(yàn)證失敗,則執(zhí)行失敗回調(diào) onFail;

效果如圖所示:
Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件,Dart語(yǔ)言與Flutter框架開(kāi)發(fā)筆記,前端、桌面端、移動(dòng)端、UI、構(gòu)建工具,Flutter,Dart,驗(yàn)證碼,滑塊驗(yàn)證,插件

3. 功能描述

3.1

4. 實(shí)現(xiàn)思路分析

4.1 分析問(wèn)題比解決問(wèn)題重要:摳出一塊圖形是摳嗎

摳出小塊圖是不是一定要從原圖像那里拷貝一塊像素?你當(dāng)然可以這樣來(lái)實(shí)現(xiàn),不過(guò)會(huì)復(fù)雜一些。不過(guò)還有一種假設(shè)是,摳出的拼圖部分不是摳出的,而是原原本本的一張圖。對(duì)比而言:

方案1: 從原圖像中拷貝一塊像素

  1. 原始圖像處理: 加載原始驗(yàn)證碼圖像,該圖像包括背景圖像和一個(gè)需要滑動(dòng)的小塊圖像。

  2. 拷貝小塊圖像: 使用Flutter的圖像處理功能,將原始圖像中的小塊圖像精確拷貝出來(lái),并將其作為驗(yàn)證碼的滑塊。這可以通過(guò)裁剪原始圖像的一部分來(lái)實(shí)現(xiàn)。

  3. 驗(yàn)證用戶拖動(dòng): 當(dāng)用戶嘗試拖動(dòng)滑塊時(shí),需要檢查滑塊的位置是否與原始圖像中的小塊位置匹配。這可以涉及比較滑塊的位置與小塊的位置是否一致。

方案2: 將兩張圖疊加

  1. 原始圖像處理: 同樣,加載原始驗(yàn)證碼圖像,包括背景圖像和一個(gè)需要滑動(dòng)的小塊圖像。

  2. 自定義布局: 使用Flutter的布局和圖層疊加功能,將兩張圖像堆疊在一起,確保小塊圖像與背景圖像的對(duì)齊。這可以通過(guò)使用Stack小部件或Positioned小部件來(lái)實(shí)現(xiàn)。

  3. 驗(yàn)證用戶拖動(dòng): 當(dāng)用戶嘗試拖動(dòng)滑塊時(shí),需要檢查滑塊的位置是否與小塊圖像的位置匹配。這可以通過(guò)比較滑塊的位置是否在小塊圖像的位置上來(lái)實(shí)現(xiàn)。

兩種方案的選擇取決于項(xiàng)目需求和個(gè)人偏好。我個(gè)人覺(jué)得方案2更加具有可操作性,因此我后續(xù)是基于這種方法來(lái)實(shí)現(xiàn)的。

4.2 知識(shí)準(zhǔn)備

這里對(duì)用到得一些 Flutter 基礎(chǔ)知識(shí)做簡(jiǎn)單介紹,方便初學(xué)讀者了解學(xué)習(xí)相關(guān)知識(shí)。

1. 堆疊布局

在Flutter中,堆疊布局(Stack布局)是一種常用的布局方式,用于將多個(gè)子部件疊加在一起。堆疊布局允許您將子部件以層疊的方式排列,每個(gè)子部件可以覆蓋或部分覆蓋其他子部件,從而創(chuàng)建復(fù)雜的布局效果。在Flutter中,堆疊布局由兩個(gè)主要組件構(gòu)成:

  1. Stack(堆疊): Stack是一個(gè)容器小部件,用于包含子部件并按照它們的繪制順序?qū)⑺鼈儻B加在一起。子部件按照從底部到頂部的順序堆疊。您可以使用children屬性來(lái)指定要疊加的子部件列表。

  2. Positioned(定位): Positioned小部件用于控制子部件在Stack中的位置。它允許您指定子部件的左、上、右和下邊距,從而將子部件精確定位在Stack上。

在堆疊布局中,子部件的位置和大小是通過(guò)Positioned小部件來(lái)控制的。每個(gè)Positioned小部件都必須包含一個(gè)左、上、右和下的屬性,以確定子部件在Stack中的位置和大小。

  • left:指定子部件的左邊距。
  • top:指定子部件的上邊距。
  • right:指定子部件的右邊距。
  • bottom:指定子部件的下邊距。

這些屬性可以設(shè)置為null,以便自動(dòng)確定位置,或者設(shè)置為具體的值,以確保子部件在Stack中的精確定位。

例如,下面的代碼展示了如何將兩個(gè)容器疊加在一起:

Stack(
  alignment: Alignment.center,
  children: [
    Container(
      width: 200,
      height: 200,
      color: Colors.blue,
    ),
    Positioned(
      left: 50,
      top: 50,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.red,
      ),
    ),
  ],
)

在上述示例中,Stack包含兩個(gè)容器,一個(gè)藍(lán)色的大容器和一個(gè)紅色的小容器。通過(guò)Positioned小部件,我們將小容器定位到了大容器的左上角。

2. Slider組件

Flutter的Slider(滑塊)組件是一個(gè)用于選擇一個(gè)范圍內(nèi)數(shù)值的交互式控件。用戶可以通過(guò)滑動(dòng)滑塊來(lái)選擇數(shù)值,這使得它在用戶界面中用于調(diào)整設(shè)置和選擇數(shù)值非常有用。

Slider組件主要有以下功能:

  1. 數(shù)值范圍選擇: Slider允許用戶在指定的數(shù)值范圍內(nèi)進(jìn)行選擇。用戶可以通過(guò)滑動(dòng)滑塊來(lái)選擇一個(gè)數(shù)值,該數(shù)值通常表示某種設(shè)置或參數(shù)。

  2. 分割刻度: Slider可以顯示刻度線,并且可以根據(jù)需要進(jìn)行分割。這些刻度線使用戶更容易準(zhǔn)確地選擇所需的數(shù)值。

  3. 標(biāo)簽顯示: Slider可以顯示當(dāng)前數(shù)值的標(biāo)簽,通常位于滑塊上方或下方。這有助于用戶了解所選擇的數(shù)值。

  4. 回調(diào)函數(shù): 您可以為Slider設(shè)置一個(gè)回調(diào)函數(shù),當(dāng)用戶拖動(dòng)滑塊時(shí),會(huì)觸發(fā)該函數(shù)。這使得您可以在數(shù)值發(fā)生變化時(shí)執(zhí)行特定的操作,如更新UI或應(yīng)用程序狀態(tài)。

  5. 自定義樣式: Slider具有豐富的自定義樣式選項(xiàng),您可以更改滑塊、軌道和刻度的顏色、形狀和大小,以適應(yīng)您的應(yīng)用程序設(shè)計(jì)。

以下是Flutter Slider組件的一些常用屬性:

  • value:表示當(dāng)前的數(shù)值,可以通過(guò)設(shè)置此值來(lái)控制Slider的位置。
  • onChanged:一個(gè)回調(diào)函數(shù),當(dāng)用戶拖動(dòng)滑塊時(shí)觸發(fā),用于處理數(shù)值的變化。
  • min:Slider的最小值。
  • max:Slider的最大值。
  • divisions:用于將Slider軌道分割為多少個(gè)離散步驟,通常與滑塊刻度一起使用。
  • label:顯示在滑塊上方或下方的標(biāo)簽,通常用于顯示當(dāng)前值。
  • activeColor:激活狀態(tài)下(滑塊被拖動(dòng))的顏色。
  • inactiveColor:非激活狀態(tài)下的顏色。
  • thumbColor:滑塊的顏色。
  • thumbShape:滑塊的形狀,可以是圓形、方形等。
  • trackHeight:軌道的高度。

例如:

class SliderExample extends StatefulWidget {
  const SliderExample({super.key});

  
  State<SliderExample> createState() => _SliderExampleState();
}

class _SliderExampleState extends State<SliderExample> {
  double _currentSliderValue = 20;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Slider')),
      body: Slider(
        value: _currentSliderValue,
        max: 100,
        divisions: 5,
        label: _currentSliderValue.round().toString(),
        onChanged: (double value) {
          setState(() {
            _currentSliderValue = value;
          });
        },
      ),
    );
  }
}

這個(gè)示例是一個(gè)基本的Flutter應(yīng)用程序,來(lái)自于Flutter官方。

  • 當(dāng)應(yīng)用程序啟動(dòng)時(shí),Slider的初始值是20。用戶可以通過(guò)按住并拖動(dòng)滑塊來(lái)選擇不同的數(shù)值。
  • 滑塊的最小值是0,最大值是100,滑塊上有5個(gè)刻度線,表示5個(gè)離散的數(shù)值。當(dāng)用戶拖動(dòng)滑塊時(shí),滑塊的值會(huì)隨著手指的拖動(dòng)而實(shí)時(shí)更新。
  • 在滑塊的上方顯示一個(gè)標(biāo)簽,顯示當(dāng)前所選數(shù)值的整數(shù)部分,例如,當(dāng)用戶將滑塊移動(dòng)到25時(shí),標(biāo)簽顯示"25"。
  • 滑塊的外觀由activeColor(激活狀態(tài)下的顏色)和inactiveColor(非激活狀態(tài)下的顏色)屬性定義。在示例中,激活狀態(tài)下的顏色為藍(lán)色,非激活狀態(tài)下的顏色為灰色。

3. Flutter 繪圖(canvas)

在Flutter中,您可以使用Canvas來(lái)進(jìn)行繪圖操作,Canvas是Flutter中的繪圖上下文,允許您在屏幕上繪制各種形狀、文本和圖像。Canvas通常與CustomPaint小部件一起使用,以在Flutter的繪圖流程中插入自定義繪圖代碼。

使用Canvas進(jìn)行繪圖的基本步驟包括:

  1. 創(chuàng)建一個(gè)CustomPaint小部件: 首先,您需要在Flutter應(yīng)用程序的UI層次結(jié)構(gòu)中插入一個(gè)CustomPaint小部件,以便將繪圖內(nèi)容放入其中。

  2. 自定義Painter: 您需要?jiǎng)?chuàng)建一個(gè)自定義的Painter類,它繼承自CustomPainter,并實(shí)現(xiàn)paint和shouldRepaint方法。paint方法是您用來(lái)實(shí)際繪制內(nèi)容的地方,shouldRepaint方法決定是否需要重新繪制。

  3. 在paint方法中繪制內(nèi)容: 在paint方法中,您可以使用Canvas對(duì)象來(lái)進(jìn)行各種繪圖操作,如繪制圖形、文本、路徑等。Canvas提供了各種方法來(lái)繪制不同類型的圖形。

例如,下面得代碼展示了如何在Flutter中使用Canvas繪制一個(gè)簡(jiǎn)單的圓形:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyCustomPainter extends CustomPainter {
  
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    final centerX = size.width / 2;
    final centerY = size.height / 2;
    final radius = size.width / 3;

    canvas.drawCircle(Offset(centerX, centerY), radius, paint);
  }

  
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Canvas繪圖示例'),
        ),
        body: Center(
          child: CustomPaint(
            size: const Size(200, 200),
            painter: MyCustomPainter(),
          ),
        ),
      ),
    );
  }
}

上述示例中,MyCustomPainter類繼承自CustomPainter,并在其paint方法中繪制了一個(gè)藍(lán)色的圓形。然后,CustomPaint小部件將MyCustomPainter作為其painter屬性的值傳遞,并在UI中顯示繪制的圓形。

通過(guò)Canvas和CustomPainter,您可以創(chuàng)建各種自定義繪圖效果,包括圖表、動(dòng)畫(huà)、自定義圖形和復(fù)雜的UI元素。Canvas提供了豐富的繪圖功能,可以滿足各種繪圖需求。

4. Flutter 裁剪(Clip)

Flutter中的裁剪(Clip)是一種用于控制Widget可見(jiàn)區(qū)域的技術(shù),它可以用來(lái)創(chuàng)建各種不同形狀和效果的UI元素。裁剪允許您定義一個(gè)區(qū)域,只有在該區(qū)域內(nèi)的部分內(nèi)容才會(huì)被顯示,超出該區(qū)域的內(nèi)容會(huì)被裁剪掉。Flutter提供了多種不同類型的裁剪小部件,以滿足各種需求。

以下是一些常見(jiàn)的Flutter裁剪小部件和其用途:

  1. ClipRect: 這是最常見(jiàn)的裁剪小部件之一,它可以將其子部件裁剪為矩形形狀。使用它可以創(chuàng)建各種矩形裁剪效果,例如將圖像限制在矩形區(qū)域內(nèi)。

  2. ClipOval: 這個(gè)小部件可以將其子部件裁剪為橢圓形狀,用于創(chuàng)建橢圓形的UI元素,如頭像或按鈕。

  3. ClipRRect: ClipRRect用于創(chuàng)建帶有圓角的矩形裁剪,可以用來(lái)創(chuàng)建圓角矩形框或卡片。

  4. ClipPath: ClipPath允許您自定義裁剪區(qū)域的形狀,通過(guò)提供一個(gè)自定義路徑來(lái)實(shí)現(xiàn)各種復(fù)雜的裁剪效果。

這里是一個(gè)示例,展示如何使用ClipRect來(lái)裁剪一個(gè)圖像以顯示在矩形區(qū)域內(nèi):

ClipRect(
  child: Image.network(
    'https://example.com/image.jpg',
    width: 200,
    height: 200,
    fit: BoxFit.cover,
  ),
)

上述示例中,ClipRect將Image小部件裁剪為矩形區(qū)域內(nèi)的可見(jiàn)部分。您可以使用其他Clip類型來(lái)創(chuàng)建不同形狀和效果的裁剪。

裁剪在創(chuàng)建各種自定義UI效果時(shí)非常有用,例如創(chuàng)建特定形狀的按鈕、卡片或背景。通過(guò)使用不同的Clip類型,您可以實(shí)現(xiàn)各種各樣的外觀和動(dòng)畫(huà)效果,從而增強(qiáng)Flutter應(yīng)用程序的用戶界面。

5. 基本實(shí)現(xiàn)

5.1 實(shí)現(xiàn)代碼

/// 作者:李俊才
/// 郵箱:291148484@163.com
/// 項(xiàng)目地址:http://thispage.tech:9680/jclee1995/flutter-jc-captcha
/// 協(xié)議:MIT
import 'dart:math';
import 'package:flutter/material.dart';

/// 驗(yàn)證碼組件
///
/// 這個(gè)組件用于顯示一個(gè)驗(yàn)證碼圖像,用戶需要滑動(dòng)滑塊以解鎖驗(yàn)證。當(dāng)驗(yàn)證成功或失敗時(shí),
/// 分別觸發(fā) [onSuccess] 或 [onFail] 回調(diào)函數(shù)。你可以設(shè)置允許的誤差范圍 [deviation]
/// 以調(diào)整驗(yàn)證的精確性。
class CaptchaWidget extends StatefulWidget {
  /// 用作驗(yàn)證圖像的URL
  final String imageUrl;

  /// 當(dāng)驗(yàn)證成功時(shí)觸發(fā)的回調(diào)函數(shù)。
  final Function() onSuccess;

  /// 當(dāng)驗(yàn)證失敗時(shí)觸發(fā)的回調(diào)函數(shù)。
  final Function() onFail;

  /// 允許的誤差范圍,用于調(diào)整驗(yàn)證的精確性。
  static double deviation = 5;

  /// 創(chuàng)建一個(gè) [CaptchaWidget] 小部件,需要指定 [imageUrl]、[onSuccess] 和 [onFail] 回調(diào)函數(shù)。
  const CaptchaWidget({
    Key? key,
    required this.imageUrl,
    required this.onSuccess,
    required this.onFail,
  }) : super(key: key);

  
  State<CaptchaWidget> createState() => _CaptchaWidgetState();
}

class _CaptchaWidgetState extends State<CaptchaWidget> {
  /// 滑塊的當(dāng)前位置。
  double _sliderValue = 0.0;

  late double _offsetRate;

  /// 用于定位的偏移值。
  late double _offsetValue;

  /// 小部件的總寬度。
  late double width;

  /// 用于確保驗(yàn)證僅僅一次有效
  bool _verified = false;

  double _generateRandomNumber() {
    // 創(chuàng)建一個(gè)Random對(duì)象
    var random = Random();

    // 生成一個(gè)介于0.1和0.9之間的隨機(jī)小數(shù)
    double randomValue = 0.1 + random.nextDouble() * 0.7;

    return randomValue;
  }

  
  void initState() {
    _offsetRate = _generateRandomNumber();

    super.initState();
  }

  
  Widget build(BuildContext context) {
    width = MediaQuery.of(context).size.width;
    _offsetValue = _offsetRate * width;
    return Column(
      children: [
        // 堆疊三層,背景圖、裁剪的拼圖、拼圖的輪廓繪圖
        Stack(
          alignment: Alignment.center,
          children: [
            // 背景圖層
            Image.network(
              widget.imageUrl,
              height: 200.0,
              fit: BoxFit.cover,
            ),
            // 背景標(biāo)記層
            CustomPaint(
              size: Size(width, 200.0),
              painter: CaptchaBorderPainter(_offsetValue),
            ),
            // 拼圖層
            Positioned(
              left: _sliderValue * width - _offsetValue,
              child: ClipPath(
                clipper: CaptchaClipper(_sliderValue, _offsetValue),
                child: Image.network(
                  widget.imageUrl,
                  height: 200.0,
                  fit: BoxFit.cover,
                ),
              ),
            ),
            // 拼圖的輪廓層
            Positioned(
              left: _sliderValue * width - _offsetValue,
              child: CustomPaint(
                size: Size(width, 200.0),
                painter: CaptchaBorderPainter(_offsetValue),
              ),
            ),
          ],
        ),
        //
        SliderTheme(
          data: SliderThemeData(
            thumbColor: Colors.white, // 滑塊顏色為白色
            activeTrackColor: Colors.green[900], // 激活軌道顏色為深綠色
            inactiveTrackColor: Colors.green[900], // 非激活軌道顏色為深綠色
            trackHeight: 10.0, // 軌道高度
            thumbShape: const RoundSliderThumbShape(
                enabledThumbRadius: 10.0), // 滑塊形狀為圓形
          ),
          child: Slider(
            value: _sliderValue,
            onChanged: (value) {
              setState(() {
                _sliderValue = value;
              });
            },
            onChangeEnd: (value) {
              if (_verified == false) {
                if (_sliderValue.abs() * width >
                        _offsetValue - CaptchaWidget.deviation &&
                    _sliderValue.abs() * width <
                        _offsetValue + CaptchaWidget.deviation) {
                  widget.onSuccess();
                  _verified = true;
                } else {
                  widget.onFail();
                  _verified = true;
                }
              }
            },
          ),
        ),
      ],
    );
  }
}

/// 用于創(chuàng)建中滑動(dòng)拼圖的自定義剪切器。
class CaptchaClipper extends CustomClipper<Path> {
  final double sliderValue;
  final double offsetValue;

  /// 創(chuàng)建一個(gè) [CaptchaClipper],需要指定 [sliderValue] 和 [offsetValue]。
  CaptchaClipper(this.sliderValue, this.offsetValue);

  
  Path getClip(Size size) {
    final path = Path();

    final rect = RRect.fromRectAndRadius(
      Rect.fromPoints(
        Offset(offsetValue + size.width * sliderValue, 60),
        Offset(
          offsetValue + size.width * sliderValue + 80,
          size.height - 40,
        ),
      ),
      const Radius.circular(10.0),
    );

    path.addRRect(rect);
    return path;
  }

  
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return false;
  }
}

class CaptchaBorderPainter extends CustomPainter {
  final double offsetValue;

  CaptchaBorderPainter(this.offsetValue);

  
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    final rect = Rect.fromPoints(
      Offset(offsetValue, 60),
      Offset(
        offsetValue + 80,
        size.height - 40,
      ),
    );

    final path = Path()
      ..addRRect(RRect.fromRectAndRadius(rect, const Radius.circular(10.0)));

    canvas.drawPath(path, paint);
  }

  
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

5.2 控制邏輯

這里的控制也就是通過(guò)Slider的位置控制上層圖片的位置,實(shí)現(xiàn)同步移動(dòng)效果。

5.3 堆疊分層邏輯

最底層:背景圖

用作背景的圖片,這張圖片要求有一定的長(zhǎng)度(比寬高),它會(huì)平鋪開(kāi)并安裝長(zhǎng)度覆蓋。而對(duì)于超出的高度則不會(huì)顯示。因此主要需要確保相對(duì)于高度而言這樣圖片長(zhǎng)度不能短了;這部分:

 // 背景圖層
Image.network(
  widget.imageUrl,
  height: 200.0,
  fit: BoxFit.cover,
),

TODO: 這一版本都使用了固定的高度,日后可以給個(gè)調(diào)整的值。

次底層:背景圖中標(biāo)注目標(biāo)輪廓

用作強(qiáng)調(diào)背景圖中對(duì)齊位置的輪廓繪圖,表示用戶操作上層圖片的目標(biāo)位置。這部分是有canvas繪圖實(shí)現(xiàn)的。:

// 背景標(biāo)記層
CustomPaint(
  size: Size(width, 200.0),
  painter: CaptchaBorderPainter(_offsetValue),
),

中層:被裁剪的圖片,即拼圖

Flutter中,可以使用ClipPath將圖片裁剪為任何想要的形狀,用起來(lái)就像Canvas繪圖一樣。這部分將一張與最底層完全重疊、完全一樣的圖片裁剪為想要的形狀(此版本已圓角矩形為例),只不過(guò)這個(gè)圖片由于是堆疊再上方,因此需要設(shè)計(jì)得小一點(diǎn)。然后再對(duì)這個(gè)被裁剪得區(qū)域移動(dòng)到最左端——從而適配滑塊一開(kāi)始是再最左端得:

// 拼圖層
Positioned(
  left: _sliderValue * width - _offsetValue,
  child: ClipPath(
    clipper: CaptchaClipper(_sliderValue, _offsetValue),
    child: Image.network(
      widget.imageUrl,
      height: 200.0,
      fit: BoxFit.cover,
    ),
  ),
),

上層:拼圖輪廓

// 拼圖的輪廓層
Positioned(
  left: _sliderValue * width - _offsetValue,
  child: CustomPaint(
    size: Size(width, 200.0),
    painter: CaptchaBorderPainter(_offsetValue),
  ),
),

5.4 水平位置確定

總水平長(zhǎng)度

總水平長(zhǎng)度是通過(guò)媒體查詢來(lái)確定的,這對(duì)于移動(dòng)設(shè)備來(lái)說(shuō),不會(huì)存在動(dòng)態(tài)改變?cè)O(shè)備寬度的問(wèn)題,因此也沒(méi)有實(shí)時(shí)媒體查詢的必要。總體長(zhǎng)度將保存在以下字段中:

/// 小部件的總寬度。
late double width;

5.5 圖片偏移邏輯

首先背景圖是不需要便宜的,需要便宜的是上面的各個(gè)堆疊層。以下的所有偏移按照相對(duì)于左側(cè)位置計(jì)算。

初始化隨機(jī)偏移量

我考慮了一個(gè)內(nèi)部的 _generateRandomNumber 方法,用于隨機(jī)生成一個(gè)總位置 0.1~0.9 之間的偏移率,用于滑動(dòng)驗(yàn)證成功的位置。代碼為:

double _generateRandomNumber() {
  // 創(chuàng)建一個(gè)Random對(duì)象
  var random = Random();

  // 生成一個(gè)介于0.1和0.9之間的隨機(jī)小數(shù)
  double randomValue = 0.1 + random.nextDouble() * 0.7;

  return randomValue;
}

這個(gè)便宜率需要在初始化狀態(tài)時(shí)固定并暫存下來(lái),放在_offsetRate中,可以使用State類的initState實(shí)現(xiàn):

  
  void initState() {
    _offsetRate = _generateRandomNumber();

    super.initState();
  }

_offsetRate 的固定對(duì)于基于Clip的CaptchaClipper類的getClip方法中沒(méi)有什么影響,應(yīng)為在Flutter中Clip是不需要總是去重新繪制的,但是在基于Canvas的CaptchaBorderPainter就不一樣了——畢竟CustomPainter類的paint方法會(huì)被不斷調(diào)用,以至于如果不固定隨機(jī)生成的_offsetRate ,則不斷調(diào)用_generateRandomNumber方法導(dǎo)致描邊位置錯(cuò)亂。實(shí)際的偏移量,無(wú)非是媒體查詢出來(lái)的寬度去乘以這個(gè)便宜率:

width = MediaQuery.of(context).size.width;
_offsetValue = _offsetRate * width;

背景標(biāo)記層偏移

背景標(biāo)記層的偏移是一個(gè)固定的偏移量,這個(gè)偏移量由初始化的_offsetValue確定就不需要改:

CustomPaint(
  size: Size(width, 200.0),
  painter: CaptchaBorderPainter(_offsetValue),
),

在 CaptchaBorderPainter 中:


  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    final rect = Rect.fromPoints(
      Offset(offsetValue, 60),
      Offset(
        offsetValue + 80,
        size.height - 40,
      ),
    );

    final path = Path()
      ..addRRect(RRect.fromRectAndRadius(rect, const Radius.circular(10.0)));

    canvas.drawPath(path, paint);
  }

可見(jiàn),offsetValue不變則水平位置再不變。

拼圖層偏移

拼圖層是對(duì)于一個(gè)和背景圖大小完全一樣的圖片的一個(gè)水平隨機(jī)位置的一小塊裁剪的,裁剪的初始有一個(gè)隨機(jī)的偏移量,和背景標(biāo)記層偏移是一樣的,就是 offsetValue,著只不過(guò)是一個(gè)初始的裁剪距離左側(cè)的偏離距離,即CaptchaClipper中:


  Path getClip(Size size) {
    final path = Path();

    final rect = RRect.fromRectAndRadius(
      Rect.fromPoints(
        Offset(offsetValue + size.width * sliderValue, 60),
        Offset(
          offsetValue + size.width * sliderValue + 80,
          size.height - 40,
        ),
      ),
      const Radius.circular(10.0),
    );

    path.addRRect(rect);
    return path;
  }

前面有一個(gè)“offsetValue + …”。就是初始在相對(duì)于原圖片左邊的偏移量。正因?yàn)橛辛诉@個(gè)量,裁剪的不是圖片左邊的一部分,但是下面滑塊卻是初始時(shí)位于最左邊的,我們需要在堆疊時(shí)將這個(gè)偏移量減去,就使得偏移的裁剪與底下的滑塊初始時(shí)是“對(duì)齊”的:

// 拼圖層
Positioned(
  // 減去偏移量與滑塊對(duì)齊
  left: _sliderValue * width - _offsetValue, 
  child: ClipPath(
    clipper: CaptchaClipper(_sliderValue, _offsetValue),
    child: Image.network(
      widget.imageUrl,
      height: 200.0,
      fit: BoxFit.cover,
    ),
  ),
),

這樣也就是表明,一開(kāi)始的位置是最左邊的位置,只有用戶滑動(dòng)滑塊才會(huì)有可能移動(dòng)到驗(yàn)證成功的位置!

拼圖的輪廓層偏移

拼圖的輪廓層偏移 和 拼圖層偏移的值始終保持相等的,但是需要注意的是,由于它們使用的技術(shù)分別是CustomClipper和Canvas,Clip的getClip 和 CustomPainter 的 paint執(zhí)行時(shí)機(jī)不同,為了使得在paint中采用和相同的offsetValue,我們將第一次隨機(jī)生成的偏移量做過(guò)緩存。

// 拼圖的輪廓層
Positioned(
  left: _sliderValue * width - _offsetValue,
  child: CustomPaint(
    size: Size(width, 200.0),
    painter: CaptchaBorderPainter(_offsetValue),
  ),
),

6. 總結(jié)、展望/后續(xù)版本

滑動(dòng)控制UI

使用Slider實(shí)現(xiàn)的滑動(dòng)控制器一些UI效果實(shí)現(xiàn)還不好看,用起來(lái)也不方便觸摸,如果改成矩形的可能會(huì)更好。下一步計(jì)劃使用繪圖來(lái)替代Slider繪制矩形風(fēng)格的滑塊和滑槽,滑塊可以使用擬物風(fēng)格的圖標(biāo)。

輪廓形狀

上面繪制的輪廓是簡(jiǎn)單的圓角矩形,不過(guò)如果改版為拼圖的常見(jiàn)形狀,比如:
Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件,Dart語(yǔ)言與Flutter框架開(kāi)發(fā)筆記,前端、桌面端、移動(dòng)端、UI、構(gòu)建工具,Flutter,Dart,驗(yàn)證碼,滑塊驗(yàn)證,插件
會(huì)更加好看一些。
可以將再下一個(gè)版本中可以考慮重新使用Canvas繪制。

自帶成功效果

如果驗(yàn)證成功后,可以在整個(gè)圖片疊加區(qū)域上面添加一個(gè)半透明白色堆疊物實(shí)現(xiàn)一個(gè)成功覆蓋層,并且在中間加一個(gè)圓形背景的勾(√)來(lái)增強(qiáng)效果。

重置驗(yàn)證碼

有時(shí)候可能用戶一次驗(yàn)證沒(méi)有成功,但也不意味著是機(jī)器人。目前可以重新構(gòu)建組件,并傳入新的圖片來(lái)。不過(guò)可以考慮一個(gè)用于直接刷洗驗(yàn)證碼的接口。

滑動(dòng)時(shí)間

為了讓用戶體驗(yàn)更加絲滑,可以考慮手勢(shì)的時(shí)間計(jì)算,一旦驗(yàn)證成功,則告訴用于“本次認(rèn)證使用了xx秒,超過(guò)了99%的用戶”。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-734096.html

到了這里,關(guān)于Flutter筆記:手寫(xiě)并發(fā)布一個(gè)人機(jī)滑動(dòng)驗(yàn)證碼插件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 手寫(xiě)一個(gè)webpack插件(plugin)

    手寫(xiě)一個(gè)webpack插件(plugin)

    熟悉 vue 和 react 的小伙伴們都知道,在執(zhí)行過(guò)程中會(huì)有各種生命周期鉤子,其實(shí)webpack也不例外,在使用webpack的時(shí)候,我們有時(shí)候需要在 webpack 構(gòu)建流程中引入自定義的行為,這個(gè)時(shí)候就可以在 hooks 鉤子中添加自己的方法。 創(chuàng)建插件 webpack 加載 webpack.config.js 中所有配置,此

    2024年02月08日
    瀏覽(30)
  • flutter開(kāi)發(fā)實(shí)戰(zhàn)-指紋、面容ID驗(yàn)證插件實(shí)現(xiàn)

    flutter開(kāi)發(fā)實(shí)戰(zhàn)-指紋、面容ID驗(yàn)證插件實(shí)現(xiàn)

    flutter開(kāi)發(fā)實(shí)戰(zhàn)-指紋、面容ID驗(yàn)證插件實(shí)現(xiàn) 在iOS開(kāi)發(fā)中,經(jīng)常出現(xiàn)需要指紋、面容ID驗(yàn)證的功能。 指紋、面容ID是一種基于用生物識(shí)別技術(shù),通過(guò)掃描用戶的面部特征來(lái)驗(yàn)證用戶身份。 在iOS中實(shí)現(xiàn)指紋、面容ID驗(yàn)證功能,步驟如下 2.1 info.plist配置 在info.plist中配置允許訪問(wèn)FAC

    2024年02月13日
    瀏覽(28)
  • 【ROS】ROS1人機(jī)界面開(kāi)發(fā):第一個(gè)最簡(jiǎn)ROS+QtGui程序(按鈕啟動(dòng)發(fā)布者)

    【ROS】ROS1人機(jī)界面開(kāi)發(fā):第一個(gè)最簡(jiǎn)ROS+QtGui程序(按鈕啟動(dòng)發(fā)布者)

    1)新建工程:Other Project -- ROS Workspace 2)設(shè)置工程名稱、路徑 3)可以通過(guò)點(diǎn)擊“Browse”來(lái)創(chuàng)建目錄 注意:使用自帶ros插件的qtcreator-ros,無(wú)法創(chuàng)建目錄、也不能選擇目錄,這是個(gè)bug,因此需要在終端手動(dòng)創(chuàng)建目錄,并將目錄路徑手動(dòng)輸入“Workspace Path”中 4)如果是作為子工

    2024年02月16日
    瀏覽(45)
  • Flutter開(kāi)發(fā)筆記 —— sqflite插件數(shù)據(jù)庫(kù)應(yīng)用

    今天在觀閱掘金大佬文章的時(shí)候,了解到了該 sqflite 插件,結(jié)合官網(wǎng)教程和自己實(shí)踐,由此總結(jié)出該文,希望對(duì)大家的學(xué)習(xí)有幫助! Flutter的 SQLite 插件。支持 iOS、Android 和 MacOS。 支持事務(wù)和batch模式 打開(kāi)時(shí)自動(dòng)進(jìn)行版本管理 插入/查詢/更新/刪除查詢的助手 iOS 和 Android 上的

    2024年02月04日
    瀏覽(34)
  • Vue3+NodeJS 接入文心一言, 發(fā)布一個(gè) VSCode 大模型問(wèn)答插件

    Vue3+NodeJS 接入文心一言, 發(fā)布一個(gè) VSCode 大模型問(wèn)答插件

    目錄 一:首先明確插件開(kāi)發(fā)方式 二:新建一個(gè)Vscode 插件項(xiàng)目 1. 官網(wǎng)教程地址 2. 一步一步來(lái)創(chuàng)建 3. 分析目錄結(jié)構(gòu)以及運(yùn)行插件 三:新建一個(gè)Vue3 項(xiàng)目,在側(cè)邊欄中展示,實(shí)現(xiàn)vscode插件 = vue項(xiàng)目 雙向消息傳遞 1. 新建vue3+vite+ts項(xiàng)目 2. 將web頁(yè)面展示在vscode側(cè)邊欄 (1) 插件項(xiàng)目

    2024年02月04日
    瀏覽(24)
  • Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(下)

    Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(下)

    Flutter筆記 完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dart吉祥物Dash 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 郵箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/134098955 另見(jiàn): 上上篇文章:《完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dart語(yǔ)言吉祥物

    2024年02月07日
    瀏覽(39)
  • Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(上)

    Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(上)

    Flutter筆記 完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dart語(yǔ)言吉祥物Dash(上) 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 郵箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/134098877 【介紹】:本文完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(上

    2024年02月07日
    瀏覽(38)
  • Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(中)

    Flutter筆記:完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(中)

    Flutter筆記 完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dart語(yǔ)言吉祥物Dash(中) 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 郵箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/134098877 【介紹】:本文完全基于Flutter繪圖技術(shù)繪制一個(gè)精美的Dash圖標(biāo)(中

    2024年02月06日
    瀏覽(25)
  • flutter 手寫(xiě)日歷組件

    flutter 手寫(xiě)日歷組件

    先看效果 ?直接上代碼 calendar_popup_view.dart custom_calendar.dart hotel_app_theme.dart RangePicker.dart? 這個(gè)文件是使用的地方 這里還使用到了? getx 的組件 從底部彈出

    2024年02月13日
    瀏覽(20)
  • Flutter 滑動(dòng)控制

    以PageView為例 pv基于scrollable進(jìn)行定制,四個(gè)完成功能的主要組件:ScrollNotification、RawGestureDetector、ScrollController和ScrollPosition、ViewPort ScrollNotification:封裝Notificaiton獲得該類通知,根據(jù)通知信息內(nèi)的偏移判斷頁(yè)面是否切換,然后回調(diào)onPageChanged RawGestureDetector:手勢(shì)收集類,Scro

    2024年02月14日
    瀏覽(14)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包