preface

This paper records the function of using Flutter to complete the gesture cipher, and the general effect is as shown in the figure below:

The function of the gesture password is relatively simple. The implementation process will be recorded in detail below, and how to publish the gesture password as a plug-in to the PUB warehouse will also be explained briefly.

start

Implementation of the above gesture password is not difficult, can be roughly divided into the following parts to complete:

  1. Draw 9 dots

  2. Draw the line of the finger

  3. Merge the above two parts

Draw the dot

We use an object-oriented approach to draw nine dots, each as a GesturePoint class, this class must provide a center coordinates and radius to draw the circle, here is the source code for this class:

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

// Gesture the dots on the cipher disk
class GesturePoint {
  // Solid center dot brush
  static finalpointPainter = Paint() .. style = PaintingStyle.fill .. color = Colors.blue;// The brush for the outer ring
  static finallinePainter = Paint() .. style = PaintingStyle.stroke .. strokeWidth =1.2
    ..color = Colors.blue;

  // Dot index, 0-9
  final int index;
  // Center coordinates
  final double centerX;
  final double centerY;
  // The radius of the central solid dot
  final double radius = 4;
  // The radius of the outer hollow ring
  final double padding = 26;

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

  // Draw the dots
  void drawCircle(Canvas canvas) {
    // Draw a dot with a solid center
    canvas.drawOval(
        Rect.fromCircle(center: Offset(centerX, centerY), radius: radius),
        pointPainter);

    // Draw the outer ring
    canvas.drawOval(
        Rect.fromCircle(center: Offset(centerX, centerY), radius: padding),
        linePainter);
  }

  // Determine whether the coordinates are in the small circle (padding is the radius)
  // This method is used to judge when the finger is sliding. Once the coordinate is inside the dot, the dot is considered selected
  bool checkInside(double x, double y) {
    var distance = sqrt(pow((x - centerX), 2) + pow((y - centerY), 2));
    return distance <= padding;
  }

  // Provide a comparison method to determine whether a point exists in the List
  // This method will be used later. When the gesture slides to a certain point, the point cannot be selected again if it has been previously touched
  @override
  bool operator= = (Object other) {
    if (other is GesturePoint) {
      return this.index == other.index &&
          this.centerX == other.centerX &&
          this.centerY == other.centerY;
    }
    return false;
  }

  // The == method must be duplicated along with the hashCode method
  @override
  int get hashCode => super.hashCode;

}
Copy the code

Note above that the GesturePoint class provides a drawCircle method for drawing itself, which will be used later in the code.

Now that we have the dot object, we need to draw nine dots on the screen in turn. Since the nine dots will not be updated, we use a StatelessWidget. (If you want the effect of sliding your finger to a dot that changes color, use the StatefulWidget to update the status.)

Use a custom stateless component to draw the 9 dots as follows:

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

// 9 dot view
class GestureDotsPanel extends StatelessWidget {
  // Indicates the width and height of the dot disk
  final double width, height;
  // Load a set of 9 dots from the outside
  final List<GesturePoint> points;
    
  // The constructor
  GestureDotsPanel(this.width, this.height, this.points);

  @override
  Widget build(BuildContext context) {
    returnContainer( width: width, height: height, child: CustomPaint( painter: _PanelPainter(points), ), ); }}// The custom Painter is used to traverse all dots from the set of dots and draw them one by one
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) {
        // Draw all the dotsp.drawCircle(canvas); }}}@override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false; // Do not allow updates

}
Copy the code

The above code is relatively simple and will not be explained in detail. If you are not familiar with the basics of Flutter drawing, you can see the introduction here: CustomPaint and Canvas.

Draw gesture path

The reason why the gesture path is drawn separately is because the dot disk is not updated, and the gesture path needs to be updated with each swipe of the finger, so the gesture path is drawn as a separate component. Obviously, this component is a stateful component that inherits the StatefulWidget to implement.

Before we start coding, we need to analyze the flow of gesture sliding:

  1. You have to listen for three different events: finger down, finger sliding, finger lifting

  2. When the finger is pressed, if it is not above any of the nine dots, the finger slide is invalid

  3. If the finger is on a certain point when it is pressed down, a straight line from that point to the current point of the finger should be drawn when the finger moves behind it. If the finger enters other dots when it moves, a straight line should be drawn between all the dots passed by the finger before, and then a straight line between the last dot and the current coordinate of the finger

  4. Each dot is allowed to be recorded only once. If a finger slides past a dot before, the dot should not be recorded when a subsequent finger slides past the dot

  5. After the finger is lifted, you need to calculate which points the finger has passed in the process of moving, and return the index of all points in the form of an array. And after the finger is raised, there is no need to draw the line between the last point and the coordinate when the finger is raised

After going through the above gesture password drawing process, we also need to understand some API of Flutter for gesture processing. In this example, we mainly use GestureDetector, which is a Widget that the Official Flutter encapsulates mobile gestures. It is very convenient to use. You can see here — Flutter Combat — Gesture Recognition

Here’s all the code to draw the gestural password path:

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

// Gestural password path view
class GesturePathView extends StatefulWidget {
  // The width and height of the gestural password path view, which must be the same as that of the dot view, is passed in by the constructor
  final double width;
  final double height;
  // The nine points in the gesture cipher, passed in by the constructor
  final List<GesturePoint> points;
  / / gestures password listener, is used to trigger when raised his finger, defined as: typedef OnGestureCompleteListener = void Function (List < int >);
  final OnGestureCompleteListener listener;

  // The constructor
  GesturePathView(this.width, this.height, this.points, this.listener);

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

class _GesturePathViewState extends State<GesturePathView> {
  // Record the last point passed by the finger in the process of pressing or sliding
  GesturePoint? lastPoint;
  // Record the coordinates when the finger slides
  Offset? movePos;
  // Record all the points passed by the finger during the slide
  List<GesturePoint> pathPoints = [];

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: CustomPaint(
        size: Size(widget.width, widget.height),    // Specify the component size
        painter: _PathPainter(movePos, pathPoints), When movePos or pathPoints are updated, the entire component needs to be updated as well
      ),
      onPanDown: _onPanDown,     // Hold down your finger
      onPanUpdate: _onPanUpdate, // Finger slide
      onPanEnd: _onPanEnd,       // Finger up
    );
  }

  // Hold down your finger
  _onPanDown(DragDownDetails e) {
    // Determine whether the selected coordinate is at a certain point
    // Note: e.lockPosition indicates the coordinate relative to the entire component
    // equitellobalposition = equitellobalposition = equitellobalposition
    final x = e.localPosition.dx;
    final y = e.localPosition.dy;
    // Determine whether to press on a certain point
    for (var p in widget.points) {
      if(p.checkInside(x, y)) { lastPoint = p; }}/ / reset pathPoints
    pathPoints.clear();
  }

  // Finger slide
  _onPanUpdate(DragUpdateDetails e) {
    // If the finger is not on a dot, the slide event is not handled
    if (lastPoint == null) {
      return;
    }
    // If the slide is on a dot, add that dot to the path
    final x = e.localPosition.dx;
    final y = e.localPosition.dy;
    // passPoint indicates whether the finger passes through a certain point. It can be null
    GesturePoint? passPoint;
    for (var p in widget.points) {
      // If the finger slides past a point and it has not been passed before, record the point
      if(p.checkInside(x, y) && ! pathPoints.contains(p)) { passPoint = p;break;
      }
    }
    setState(() {
      // If the pass point is empty, then the lastPoint and pathPoints need to be refreshed, triggering the update of the entire component
      if(passPoint ! =null) {
        lastPoint = passPoint;
        pathPoints.add(passPoint);
      }
      // Update the value of movePos
      movePos = Offset(x, y);
    });
  }

  // Finger up
  _onPanEnd(DragEndDetails e) {
    setState(() {
      // Set movePos to null to prevent a line from being drawn between the last point and the point when the finger was raised
      movePos = null;
    });
    // Call the Listener to return all the points the gesture passed through
    List<int> arr = [];
    if (pathPoints.isNotEmpty) {
      for (var value inpathPoints) { arr.add(value.index); } } widget.listener(arr); }}// Draw the gesture path
class _PathPainter extends CustomPainter {
  // The current coordinate of the finger
  final Offset? movePos;
  // The finger passes through the collection of points
  final List<GesturePoint> pathPoints;

  // Path brush
  finalpathPainter = 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);
  }

  // Draw a straight line between all the points through which the finger moves
  _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); }}// Draw a real-time line between the last point and the current finger coordinate
  _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;
}
Copy the code

Combine 9 dots and gesture paths

To combine these two components, you need to use the Stack component. The code is relatively simple.

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'; / / define gestures password callback listener 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(); Double deltaW = widget.width / 4; 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) ], ); }}Copy the code

The use of gesture password components

At this point, the gestural password is developed and easy to use. The preview at the beginning of this article uses the following code:

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}")],),); }}Copy the code

Upload custom components to the PUB repository

The process of uploading custom components to the Pub repository is not very complicated. Here is the official document: dart.cn/tools/pub/p…

Here are the main steps for publishing a plug-in to the PUB repository:

  1. (This step is optional but recommended) Create a new project on Github and push our code to the repository. (You can use the GitHub repository address directly when configuring the homepage later)
  2. Create a readme.md file in the project root directory and write an introduction to the project and the use of the plug-in you wrote
  3. Create a changelog. md file in the project root directory and record what was updated with each different version
  4. Create a new LICENSE file in the project root directory. What open source protocol does the plug-in use
  5. Modify the projectpubspec.yamlFile, the main modification points are:
    homepage: "Fill in the project home page address. You can use the Github repository address here."
    publish_to: 'https://pub.dev' This configuration indicates where to publish the plug-in
    version: 0.02. # Add-on version. Change this version every time you update it
    Copy the code
  6. Execute in the project root directorydart pub publishFor the first time, the following message is displayed:
    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=8183068 855108-8grd2eg9tjq9f38os6f1urbcvsq39u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A55486&code_chal lenge=V1-sGcrLkXljXXpOyJdqf8BJfRzBcUQaH9G1m329_M&code_challenge_method=S2536&scope=openid+https%3A%2F%2Fwww.googleapis.c om%2Fauth%2Fuserinfo.email Then click "Allow access". Waiting for your authorization...Copy the code

    Clicking on the above link will open the browser and authorize it. After the authorization is granted, the console prompts that the upload is complete.

Afterword.

This record of the Flutter gesture password has been uploaded to pub: pub. Dev /packages/fl…

The source code for the project is hosted on GitHub: github.com/yubo725/flu…

If you find it helpful, please do not hesitate to give a Star support.

The most basic way to achieve the gesture password is the above process, in this case I did not do too much encapsulation, also do not provide more configuration items such as gesture password dot color, path line color, thickness and so on, you can according to their own projects, copy the code and make the corresponding changes. In addition, gesture password saving and verification is not within the scope of this record, you can do some encryption according to the final integer array and save to the local, in the verification password, do string matching.