最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter手勢密碼的實現(xiàn)示例(附demo)

 更新時間:2021年08月17日 10:35:51   作者:yuxiyu  
本文主要介紹了Flutter手勢密碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

前言

本篇記錄的是使用Flutter完成手勢密碼的功能,大致效果如下圖所示:

該手勢密碼的功能比較簡單,下面會詳細記錄實現(xiàn)的過程,另外還會簡單說明如何將該手勢密碼作為插件發(fā)布到pub倉庫。

開始

實現(xiàn)上面的手勢密碼并不難,大致可以拆分成如下幾部分來完成:

  • 繪制9個圓點
  • 繪制手指滑動的線路
  • 合并以上兩個部分

繪制圓點

我們使用面向?qū)ο蟮姆绞絹硖幚?個圓點的繪制,每個圓點作為一個GesturePoint類,這個類要提供一個圓心坐標和半徑才能畫出圓形來,這里先放上這個類的源碼:

// point.dart
import 'package:flutter/material.dart';
import 'dart:math';

// 手勢密碼盤上的圓點
class GesturePoint {
  // 中心實心圓點的畫筆
  static final pointPainter = Paint()
    ..style = PaintingStyle.fill
    ..color = Colors.blue;

  // 外層圓環(huán)的畫筆
  static final linePainter = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 1.2
    ..color = Colors.blue;

  // 圓點索引,0-9
  final int index;
  // 圓心坐標
  final double centerX;
  final double centerY;
  // 中心實心圓點的半徑
  final double radius = 4;
  // 外層空心的圓環(huán)半徑
  final double padding = 26;

  GesturePoint(this.index, this.centerX, this.centerY);

  // 繪制小圓點
  void drawCircle(Canvas canvas) {
    // 繪制中心實心的圓點
    canvas.drawOval(
        Rect.fromCircle(center: Offset(centerX, centerY), radius: radius),
        pointPainter);

    // 繪制外層的圓環(huán)
    canvas.drawOval(
        Rect.fromCircle(center: Offset(centerX, centerY), radius: padding),
        linePainter);
  }

  // 判斷坐標是否在小圓內(nèi)(padding為半徑)
  // 該方法用于在手指滑動時做判斷,一旦坐標處于圓點內(nèi)部,則認為選中該圓點
  bool checkInside(double x, double y) {
    var distance = sqrt(pow((x - centerX), 2) + pow((y - centerY), 2));
    return distance <= padding;
  }

  // 提供比較方法,用于判斷List中是否存在某個點
  // 這個方法會在后面用到,當手勢滑動到某個點時,如果之前滑動到過這個點,則這個點不能再被選中
  @override
  bool operator ==(Object other) {
    if (other is GesturePoint) {
      return this.index == other.index &&
          this.centerX == other.centerX &&
          this.centerY == other.centerY;
    }
    return false;
  }

  // 復(fù)寫==方法時必須同時復(fù)寫hashCode方法
  @override
  int get hashCode => super.hashCode;

}

上面需要注意的是,GesturePoint類提供了一個drawCircle方法用于繪制自身,將會在后面的代碼中用到。

有了圓點這個對象,我們還需要將9個圓點依次畫在屏幕上,由于這9個圓點后續(xù)是不再更新的,所以使用一個StatelessWidget即可。(如果你需要做成手指滑動到某個圓點,該圓點變色的效果,則需要用StatefulWidget組件去更新狀態(tài)。)

下面使用一個自定義的無狀態(tài)組件去畫這9個圓點,代碼如下:

// panel.dart
import 'package:flutter/material.dart';
import 'package:flutter_gesture_password/point.dart';

// 9個圓點視圖
class GestureDotsPanel extends StatelessWidget {
  // 表示圓點盤的寬高
  final double width, height;
  // 裝載9個圓點的集合,從外部傳入
  final List<GesturePoint> points;
    
  // 構(gòu)造方法
  GestureDotsPanel(this.width, this.height, this.points);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: width,
      height: height,
      child: CustomPaint(
        painter: _PanelPainter(points),
      ),
    );
  }
}

// 自定義的Painter,用于從圓點集合中遍歷所有圓點并依次畫出
class _PanelPainter extends CustomPainter {
  final List<GesturePoint> points;

  _PanelPainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    if (points.isNotEmpty) {
      for (var p in points) {
        // 畫出所有的圓點
        p.drawCircle(canvas);
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false; // 不讓更新

}

以上代碼比較簡單,就不做詳細說明了,如果對Flutter繪圖基礎(chǔ)還不了解的同學,可以看看這里的介紹:《Flutter實戰(zhàn)——自繪組件 (CustomPaint與Canvas)

繪制手勢路徑

之所以手勢路徑要單獨拿出來繪制,沒有跟上面的9個小圓點盤放一起,是因為我們的圓點盤是不更新的,而手勢路徑需要在手指的每一次滑動中更新,所以單獨將手勢路徑作為一個組件。顯然這個組件是一個有狀態(tài)的組件,需要繼承StatefulWidget來實現(xiàn)。

在開始編碼前,我們需要分析手勢滑動的流程:

  • 必須監(jiān)聽手指按下,手指滑動,手指抬起三種不同的事件
  • 手指按下時,如果不在9個圓點中的任意一個上面,則手指滑動是無效的
  • 手指按下時若在某個點上,則后面手指移動時,需要繪制從那個點到手指當前的一條直線,若手指移動過程中進入其他圓點,則需要先繪制之前手指經(jīng)過的所有圓點間的直線,再繪制最后一個圓點到手指當前滑動的坐標間的直線
  • 每個圓點只允許被記錄一次,若之前手指滑動經(jīng)過某個點,后面手指再經(jīng)過該點時,該點不應(yīng)該被記錄
  • 手指抬起后,需要計算手指移動過程中經(jīng)過了哪些點,以數(shù)組的形式返回所有點的索引。且手指抬起后,不需要繪制最后一個點到手指抬起時的坐標間的直線

梳理了上面的手勢密碼繪制流程后,我們還需要了解Flutter處理手勢的一些API,本例子中主要使用的GestureDetector,這是Flutter官方對移動端手勢封裝的一個Widget,使用起來非常方便,如果有不太了解的同學,可以參考這里——《Flutter實戰(zhàn)——手勢識別

下面放上繪制手勢密碼路徑的所有代碼:

// path.dart
import 'package:flutter/material.dart';
import 'package:flutter_gesture_password/gesture_view.dart';
import 'package:flutter_gesture_password/point.dart';

// 手勢密碼路徑視圖
class GesturePathView extends StatefulWidget {
  // 手勢密碼路徑視圖的寬高,需要跟圓點視圖保持一致,由構(gòu)造方法傳入
  final double width;
  final double height;
  // 手勢密碼中的9個點,由構(gòu)造方法傳入
  final List<GesturePoint> points;
  // 手勢密碼監(jiān)聽器,用于在手指抬起時觸發(fā),其定義為:typedef OnGestureCompleteListener = void Function(List<int>);
  final OnGestureCompleteListener listener;

  // 構(gòu)造方法
  GesturePathView(this.width, this.height, this.points, this.listener);

  @override
  State<StatefulWidget> createState() => _GesturePathViewState();
}

class _GesturePathViewState extends State<GesturePathView> {
  // 記錄手指按下或者滑動過程中,經(jīng)過的最后一個點
  GesturePoint? lastPoint;
  // 記錄手指滑動時的坐標
  Offset? movePos;
  // 記錄手指滑動過程中所有經(jīng)過的點
  List<GesturePoint> pathPoints = [];

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: CustomPaint(
        size: Size(widget.width, widget.height),    // 指定組件大小
        painter: _PathPainter(movePos, pathPoints), // 指定組件的繪制者,當movePos或者pathPoints更新時,整個組件也需要更新
      ),
      onPanDown: _onPanDown,     // 手指按下
      onPanUpdate: _onPanUpdate, // 手指滑動
      onPanEnd: _onPanEnd,       // 手指抬起
    );
  }

  // 手指按下
  _onPanDown(DragDownDetails e) {
    // 判斷按下的坐標是否在某個點上
    // 注意:e.localPosition表示的坐標為相對整個組件的坐標
    // e.globalPosition表示的坐標為相對整個屏幕的坐標
    final x = e.localPosition.dx;
    final y = e.localPosition.dy;
    // 判斷是否按在某個點上
    for (var p in widget.points) {
      if (p.checkInside(x, y)) {
        lastPoint = p;
      }
    }
    // 重置pathPoints
    pathPoints.clear();
  }

  // 手指滑動
  _onPanUpdate(DragUpdateDetails e) {
    // 如果手指按下時不在某個圓點上,則不處理滑動事件
    if (lastPoint == null) {
      return;
    }
    // 滑動時如果在某個圓點上,則將該圓點加入路徑中
    final x = e.localPosition.dx;
    final y = e.localPosition.dy;
    // passPoint代表手指滑動時是否經(jīng)過某個點,可為空
    GesturePoint? passPoint;
    for (var p in widget.points) {
      // 如果手指滑動經(jīng)過某個點,且這個點之前沒有經(jīng)過,則記錄下這個點
      if (p.checkInside(x, y) && !pathPoints.contains(p)) {
        passPoint = p;
        break;
      }
    }
    setState(() {
      // 如果經(jīng)過點部為空,則需要刷新lastPoint和pathPoints,觸發(fā)整個組件的更新
      if (passPoint != null) {
        lastPoint = passPoint;
        pathPoints.add(passPoint);
      }
      // 更新movePos的值
      movePos = Offset(x, y);
    });
  }

  // 手指抬起
  _onPanEnd(DragEndDetails e) {
    setState(() {
      // 將movePos設(shè)置為空,防止畫出最后一個點到手指抬起時的坐標間的直線
      movePos = null;
    });
    // 調(diào)用Listener,返回手勢經(jīng)過的所有點
    List<int> arr = [];
    if (pathPoints.isNotEmpty) {
      for (var value in pathPoints) {
        arr.add(value.index);
      }
    }   
    widget.listener(arr);
  }
}

// 繪制手勢路徑
class _PathPainter extends CustomPainter {
  // 手指當前的坐標
  final Offset? movePos;
  // 手指經(jīng)過點集合
  final List<GesturePoint> pathPoints;

  // 路徑畫筆
  final pathPainter = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 6
    ..strokeCap = StrokeCap.round
    ..color = Colors.blue;

  _PathPainter(this.movePos, this.pathPoints);

  @override
  void paint(Canvas canvas, Size size) {
    _drawPassPath(canvas);
    _drawRTPath(canvas);
  }

  // 繪制手指一動過程中,經(jīng)過的所有點之間的直線
  _drawPassPath(Canvas canvas) {
    if (pathPoints.length <= 1) {
      return;
    }
    for (int i = 0; i < pathPoints.length - 1; i++) {
      var start = pathPoints[i];
      var end = pathPoints[i + 1];
      canvas.drawLine(Offset(start.centerX, start.centerY),
          Offset(end.centerX, end.centerY), pathPainter);
    }
  }

  // 繪制實時的,最后一個經(jīng)過點和當前手指坐標間的直線
  _drawRTPath(Canvas canvas) {
    if (pathPoints.isNotEmpty && movePos != null) {
      var lastPoint = pathPoints.last;
      canvas.drawLine(Offset(lastPoint.centerX, lastPoint.centerY), movePos!, pathPainter);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

組合9個圓點盤和手勢路徑

組合這兩個組件需要用到Stack組件,代碼比較簡單,直接上代碼了:

import 'package:flutter/material.dart';
import 'package:flutter_gesture_password/path.dart';
import 'package:flutter_gesture_password/point.dart';
import 'package:flutter_gesture_password/panel.dart';

// 定義手勢密碼回調(diào)監(jiān)聽器
typedef OnGestureCompleteListener = void Function(List<int>);

class GestureView extends StatefulWidget {
  final double width, height;
  final OnGestureCompleteListener listener;

  GestureView({required this.width, required this.height, required this.listener});

  @override
  State<StatefulWidget> createState() => _GestureViewState();
}

class _GestureViewState extends State<GestureView> {
  List<GesturePoint> _points = [];

  @override
  void initState() {
    super.initState();
    // 計算9個圓點的位置坐標
    double deltaW = widget.width / 4;
    double deltaH = widget.height / 4;
    for (int row = 0; row < 3; row++) {
      for (int col = 0; col < 3; col++) {
        int index = row * 3 + col;
        var p = GesturePoint(index, (col + 1) * deltaW, (row + 1) * deltaH);
        _points.add(p);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        GestureDotsPanel(widget.width, widget.height, _points),
        GesturePathView(widget.width, widget.height, _points, widget.listener)
      ],
    );
  }
}

手勢密碼組件的使用

到這里,手勢密碼就開發(fā)完成了,使用起來也非常簡單,本文開篇的預(yù)覽圖使用的如下代碼:

import 'package:flutter/material.dart';
import 'package:flutter_gesture_password/gesture_view.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gesture password',
      home: _Home(),
    );
  }
}

class _Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomeState();
}

class _HomeState extends State<_Home> {
  List<int>? pathArr;

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      appBar: AppBar(
        title: Text('Gesture password'),
      ),
      body: Column(
        children: [
          GestureView(
            width: screenWidth,
            height: screenWidth,
            listener: (arr) {
              setState(() {
                pathArr = arr;
              });
            },
          ),
          Text("${pathArr == null ? '' : pathArr}")
        ],
      ),
    );
  }
}

上傳自定義組件到pub倉庫

上傳自定義組件到Pub倉庫的流程不算很復(fù)雜,這里先放上官方文檔:https://dart.cn/tools/pub/publishing
下面整理發(fā)布插件到pub倉庫的主要步驟:

(這一步非必須但是建議)在github上新建一個項目,并將我們寫的代碼push到該倉庫。(后面配置homepage時可以直接使用GitHub倉庫地址)

在項目根目錄下創(chuàng)建README.md文件,在其中編寫對于項目的一些介紹,以及你編寫的插件的用法
在項目根目錄下創(chuàng)建CHANGELOG.md文件,記錄每個不同版本更新了什么
在項目根目錄下新建一個LICENSE文件,表明該插件使用什么開源協(xié)議
修改項目中的pubspec.yaml文件,主要修改點有:

homepage: 「填寫項目主頁地址,這里可以直接用github倉庫地址」
publish_to: 'https://pub.dev' # 這個配置表示要把插件發(fā)布到哪里
version: 0.0.2 # 插件版本,每次更新記得修改這個version

在項目根目錄下執(zhí)行dart pub publish,首次執(zhí)行會出現(xiàn)如下提示:

Package has 2 warnings.. Do you want to publish xxx 0.0.1 (y/N)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=8183068855108-8grd2eg9tjq9f38os6f1urbcvsq39u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A55486&code_challenge=V1-sGcrLkXljXXpOyJdqf8BJfRzBcUQaH9G1m329_M&code_challenge_method=S2536&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".

Waiting for your authorization...

點擊上面的鏈接會打開瀏覽器,授權(quán)即可。授權(quán)通過后,控制臺會提示上傳完成等信息。

后記

本篇記錄的Flutter手勢密碼已經(jīng)上傳到pub倉庫,地址為:https://pub.dev/packages/flutter_gesture_password
該項目的源碼已托管至GitHub:https://github.com/yubo725/flutter-gesture-password

手勢密碼的最基本的實現(xiàn)方式就是上面的過程了,在本例中我并未做過多的封裝,也沒有提供更多的配置項比如手勢密碼圓點顏色,路徑線條顏色、粗細等等,這些大家可以根據(jù)自己的項目,自行拷貝代碼并做相應(yīng)修改。另外,手勢密碼的保存與校驗不在本篇記錄范圍內(nèi),大家可以根據(jù)最終的整型數(shù)組來做一些加密之類并保存到本地,在校驗密碼時,做字符串匹配即可。

到此這篇關(guān)于Flutter手勢密碼的實現(xiàn)示例(附demo)的文章就介紹到這了,更多相關(guān)Flutter手勢密碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android AlertDialog對話框用法示例

    Android AlertDialog對話框用法示例

    這篇文章主要介紹了Android AlertDialog對話框用法,結(jié)合實例形式分析了AlertDialog對話框的功能及常見使用技巧,需要的朋友可以參考下
    2016-06-06
  • Flutter深色模式適配的實現(xiàn)

    Flutter深色模式適配的實現(xiàn)

    這篇文章主要介紹了Flutter深色模式適配的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Android中的廣播和廣播接收器代碼實例

    Android中的廣播和廣播接收器代碼實例

    這篇文章主要介紹了Android中的廣播和廣播接收器代碼實例,本文講解了定義一個廣播接收器、發(fā)送廣播,定義好action標志、用Intent發(fā)送、注冊只接收指定action的廣播接收器、取消該廣播接收器等操作代碼實例,需要的朋友可以參考下
    2015-05-05
  • JetpackCompose Navigation導(dǎo)航實現(xiàn)流程

    JetpackCompose Navigation導(dǎo)航實現(xiàn)流程

    Navigation是Jetpack用于Android導(dǎo)航的組件,作用是處理頁面跳轉(zhuǎn),以及頁面跳轉(zhuǎn)過程中的交互。使用Navigation,你就需要為每個頁面設(shè)定一條唯一路徑,它是一個String常量,形式是DeepLink的樣子,從一個頁面跳轉(zhuǎn)到另一個頁面,它通過輸入目的地的路徑進行轉(zhuǎn)跳
    2023-01-01
  • Android 中Fragment與Activity通訊的詳解

    Android 中Fragment與Activity通訊的詳解

    這篇文章主要介紹了Android 中Fragment與Activity通訊的詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握如何通信的,需要的朋友可以參考下
    2017-10-10
  • Android使用Retrofit實現(xiàn)自定義Converter解析接口流程詳解

    Android使用Retrofit實現(xiàn)自定義Converter解析接口流程詳解

    Retrofit是一個RESTful的HTTP網(wǎng)絡(luò)請求框架的封裝,網(wǎng)絡(luò)請求的工作本質(zhì)上是OkHttp完成,而Retrofit僅負責網(wǎng)絡(luò)請求接口的封裝
    2023-03-03
  • android獲取圖片尺寸的兩種方式及bitmap的縮放操作

    android獲取圖片尺寸的兩種方式及bitmap的縮放操作

    這篇文章主要介紹了android獲取圖片尺寸的兩種方式及bitmap的縮放操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • android自定義控件ImageView實現(xiàn)圓形圖片

    android自定義控件ImageView實現(xiàn)圓形圖片

    這篇文章主要為大家詳細介紹了android自定義控件ImageView實現(xiàn)圓形圖片,適用于用戶頭像,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • 詳解Android使用Handler造成內(nèi)存泄露的分析及解決方法

    詳解Android使用Handler造成內(nèi)存泄露的分析及解決方法

    這篇文章主要介紹了詳解Android使用Handler造成內(nèi)存泄露的分析及解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • Android中AOP的應(yīng)用實踐之過濾重復(fù)點擊

    Android中AOP的應(yīng)用實踐之過濾重復(fù)點擊

    這篇文章主要給大家介紹了關(guān)于Android中AOP的應(yīng)用實踐之過濾重復(fù)點擊的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者們具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-09-09

最新評論

保定市| 山东省| 连山| 平舆县| 长汀县| 郁南县| 井冈山市| 新巴尔虎右旗| 永清县| 禄劝| 搜索| 嘉荫县| 馆陶县| 柯坪县| 都江堰市| 正定县| 澄江县| 于都县| 深水埗区| 宣汉县| 斗六市| 彭州市| 榆社县| 自治县| 双城市| 嵩明县| 焉耆| 安康市| 松阳县| 平远县| 西安市| 凌源市| 马山县| 万年县| 巢湖市| 高阳县| 独山县| 沂源县| 如东县| 玉田县| 尼勒克县|