Android 手势解锁详解(包括一次解锁、二次设置密码)

最近看到手势解锁功能,网上有一些大牛写了很多源码,不过功能或多或少对自己的项目有些不同,琢磨着自己也写一个,技术还不到家,有些东西是参照网上的demo大笑

主要自定义View如下:

package com.example.androidgesture;
//                  _ooOoo_
//                 o8888888o
//                 88" . "88
//                 (| -_- |)
//                 O\  =  /O
//              ____/`---'\____
//            .'  \\|     |//  `.
//           /  \\|||  :  |||//  \
//          /  _||||| -:- |||||-  \
//          |   | \\\  -  /// |   |
//          | \_|  ''\---/''  |   |
//           \  .-\__  `-`  ___/-. /
//         ___`. .'  /--.--\  `. . __
//      ."" '<  `.___\_<|>_/___.'  >'"".
//    | | :  `- \`.;`\ _ /`;.`/ - ` : | |
//    \  \ `-.   \_ __\ /__ _/   .-` /  /
//=====`-.____`-.___\_____/___.-`____.-'======
//                  `=---='
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         佛祖保佑       永无BUG

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * 作者:huangl on 2016-12-02 14:59
 * 邮箱:278168565@qq.com
 * <p>
 * 类说明:
 */
public class GestureCustomView extends RelativeLayout {
    private String TAG = GestureCustomView.class.getSimpleName();
    private int LINE_NUMBER;   //行数
    private int COLUMN_NUMBER; //列数
    private GestureRound gestureViews[][];   //二维数组保存圆点总数
    private int gestureViewWidth;       //每个圆点的宽度
    private int gestureBetweenWidth;    //相邻圆点的距离
    private GestureData gestureData;
    private Context context;

    private Paint paintLine;  //画连接线的画笔
    private Path pathLine; //画连接线的画图
    private float moveX; //移动时X点坐标
    private float moveY; //移动时Y点坐标
    private float startX; //连接线起始点X坐标
    private float startY; //连接线起始点Y坐标
    private List<GestureRound> list; //保存已经选中的圆点
    private StringBuffer orderData; //保存选中圆点的顺序
    private String fristData;   //保存第一次选择的数据,用来判断第二次选择的数据,是否跟第一次一样
    private boolean isRound; //用来判断第一次按下去的时候,是否在某一个圆点内
    private int ID_NUMBER = 130059; //用来设置每个圆点的ID,可随便设置,防止在自定义其他View的时候,ID重复
    private int ROUND_NUMBER; //每次最少选中圆点个数
    private boolean isAgain = true; //是否需要二次选择
    private OnResultListener resultListener; //回调接口
    private boolean isClick = true; //是否能够点击选择,用于当第一次选择完成后,直线还没消失的时候,不能直接第二次选择


    public GestureCustomView(Context context) {
        this(context, null);
    }

    public GestureCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GestureCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public GestureCustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.Gesture_Styles);
        gestureData = new GestureData();
        gestureData.setLine_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Line_Number, getResources().getInteger(R.integer.Line_Number)));
        gestureData.setColumn_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Column_Number, getResources().getInteger(R.integer.Column_Number)));
        gestureData.setBorderWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_BorderWidth, Float.parseFloat(getResources().getString(R.string.BorderWidth))));
        gestureData.setFilletWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_FilletWidth, Float.parseFloat(getResources().getString(R.string.FilletWidth))));
        gestureData.setBetweenWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_BetweenWidth, Float.parseFloat(getResources().getString(R.string.BetweenWidth))));
        gestureData.setAgain(attributes.getBoolean(R.styleable.Gesture_Styles_Gesture_isAgain, getResources().getBoolean(R.bool.isAgain)));
        gestureData.setRound_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Round_Number, getResources().getInteger(R.integer.Round_Number)));
        gestureData.setDelay(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Delay, getResources().getInteger(R.integer.Delay)));
        gestureData.setLineWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_LineWidth, Float.parseFloat(getResources().getString(R.string.LineWidth))));
        gestureData.setDefaultBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultBorderColor, getResources().getColor(R.color.DefaultBorderColor)));
        gestureData.setDefaultFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultFilletColor, getResources().getColor(R.color.DefaultFilletColor)));
        gestureData.setDefaultExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultExcircleColor, getResources().getColor(R.color.DefaultExcircleColor)));
        gestureData.setSelectBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectBorderColor, getResources().getColor(R.color.SelectBorderColor)));
        gestureData.setSelectFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectFilletColor, getResources().getColor(R.color.SelectFilletColor)));
        gestureData.setSelectExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectExcircleColor, getResources().getColor(R.color.SelectExcircleColor)));
        gestureData.setSelectLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectLineColor, getResources().getColor(R.color.SelectLineColor)));
        gestureData.setErrorBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorBorderColor, getResources().getColor(R.color.ErrorBorderColor)));
        gestureData.setErrorFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorFilletColor, getResources().getColor(R.color.ErrorFilletColor)));
        gestureData.setErrorExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorExcircleColor, getResources().getColor(R.color.ErrorExcircleColor)));
        gestureData.setErrorLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorLineColor, getResources().getColor(R.color.ErrorLineColor)));
        gestureData.setCorrectBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectBorderColor, getResources().getColor(R.color.CorrectBorderColor)));
        gestureData.setCorrectFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectFilletColor, getResources().getColor(R.color.CorrectFilletColor)));
        gestureData.setCorrectExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectExcircleColor, getResources().getColor(R.color.CorrectExcircleColor)));
        gestureData.setCorrectLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectLineColor, getResources().getColor(R.color.CorrectLineColor)));
        this.context = context;
        paintLine = new Paint(Paint.ANTI_ALIAS_FLAG); //初始化锯齿
        paintLine.setStyle(Paint.Style.STROKE); //空心
        paintLine.setStrokeCap(Paint.Cap.ROUND); //设置笔刷的图形样式,圆形样式
        paintLine.setStrokeJoin(Paint.Join.ROUND);//设置绘制时各图形的结合方式,圆形效果
        pathLine = new Path();
        list = new ArrayList<>();
        ROUND_NUMBER = gestureData.getRound_Number();//每次最少选中圆点个数
        isAgain = gestureData.isAgain(); //是否需要二次选择
        LINE_NUMBER = gestureData.getLine_Number(); //每行圆点数目
        COLUMN_NUMBER = gestureData.getColumn_Number();//每列圆点数目
        gestureViews = new GestureRound[LINE_NUMBER][COLUMN_NUMBER];
        orderData = new StringBuffer();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        measuredWidth = measuredWidth < measuredHeight ? measuredWidth : measuredHeight;

        //每个圆点的宽度
        gestureViewWidth = (int) (COLUMN_NUMBER * measuredWidth * 1.0f / ((COLUMN_NUMBER + 1) * COLUMN_NUMBER + 1));
        //每行之间的距离
        gestureBetweenWidth = (int) (gestureViewWidth * gestureData.getBetweenWidth());
        //设置直线的宽度
        paintLine.setStrokeWidth(gestureViewWidth * gestureData.getLineWidth());

        for (int i = 0; i < LINE_NUMBER; i++) {  //每一行
            for (int j = 0; j < COLUMN_NUMBER; j++) { //每一列
                int ID = i + j + (COLUMN_NUMBER - 1) * i + ID_NUMBER + 1;
                GestureRound gestureRound = new GestureRound(context, gestureData);
                gestureRound.setId(ID);  //设置每一个圆点的ID,从ID_NUMBER+1开始
                LayoutParams params = new LayoutParams(gestureViewWidth, gestureViewWidth);
                // 第一列设置LEFT,其他列不设置。
                // 第一行设置TOP,其他行不设置。
                // 最后一列不设置RIGHT,其他列设置。
                // 最后一行不设置BOTTOM,其他行设置。
                params.setMargins((COLUMN_NUMBER == 0) ? gestureBetweenWidth : 0,
                        (LINE_NUMBER == 0) ? gestureBetweenWidth : 0,
                        (j == (COLUMN_NUMBER - 1)) ? 0 : gestureBetweenWidth, (i == LINE_NUMBER - 1) ? 0 : gestureBetweenWidth);
                params.addRule(RIGHT_OF, (j != 0) ? ID - 1 : 0); //第一列不设置,其他圆点都在上一个圆点右侧
                params.addRule(BELOW, (i != 0) ? ID - COLUMN_NUMBER : 0); //第一行不设置,其他圆点都在上一行下面
                gestureRound.setLayoutParams(params);
                gestureViews[i][j] = gestureRound;
                addView(gestureRound);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (isRound) { //若第一次按下去不在圆点内,则不画直线
            if (pathLine != null)
                canvas.drawPath(pathLine, paintLine);
            if (this.moveX != 0 && this.moveY != 0) {   //移动后才开始划线
                canvas.drawLine(this.startX, this.startY, this.moveX, this.moveY, paintLine);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: //按下
                GestureRound ro = getRound(x, y);
                if (ro != null && isClick) { //isRound 判断按下去的时候,是否在圆点内
                    isClick = false;
                    paintLine.setColor(gestureData.getSelectLineColor());
                    ro.setState(1); //改变选中圆点的状态
                    isRound = true;
                    list.add(ro); //把选中的圆点保存起来
                    getStartPosition(ro); //获取直线的起始位置的XY值
                    pathLine.moveTo(this.startX, this.startY); //设置直线的起始位置
                    orderData.append(ro.getId() - ID_NUMBER); //保存选中圆点的顺序
                } else {
                    isRound = false;
                }
                break;
            case MotionEvent.ACTION_MOVE: //移动
                if (isRound == true) { //能否画直线
                    this.moveX = x;
                    this.moveY = y;
                    GestureRound round = getRound(this.moveX, this.moveY);
                    if (round != null && !list.contains(round)) {  //判断round是否为空以及round是否已经选择过了
                        round.setState(1); //改变选中圆点的状态
                        getStartPosition(round);//重新获取直线的起始位置的XY值
                        list.add(round); //把选中圆点保存起来
                        orderData.append(round.getId() - ID_NUMBER); //保存选中圆点的顺序
                        pathLine.lineTo((round.getLeft() + round.getRight()) / 2, (round.getTop() + round.getBottom()) / 2);
                    }
                }
                break;
            case MotionEvent.ACTION_UP: //放开
                if (isRound == true) {
                    //将最后的直线位置定位到最后一个圆点
                    this.moveX = this.startX;
                    this.moveY = this.startY;
                    if (list.size() >= ROUND_NUMBER) { //选择的圆点个数是否超过最少选中圆点个数
                        if (isAgain) { //是否需要二次选择
                            if (fristData == null) { //是否第一次选择圆点
                                fristData = orderData.toString();
                            } else {
                                if (fristData.equals(orderData.toString())) { //第一次选择圆点顺序跟第二次选择圆点顺序是否相同
                                    changeMode(2); //成功
                                    paintLine.setColor(gestureData.getCorrectLineColor());
                                } else {
                                    changeMode(3); //错误
                                    paintLine.setColor(gestureData.getErrorLineColor());
                                }
                            }
                        }
                        resultListener.Result(orderData.toString()); //
                    } else
                        Toast.makeText(context, "至少连接" + ROUND_NUMBER + "个点,请重试!", Toast.LENGTH_SHORT).show();
                    orderData.delete(0, orderData.length());
                    restoreState();//还原状态
                }
                break;
            default:
                break;
        }
        invalidate(); //刷新界面,才能调用dispatchDraw方法
        return true;  //返回true 才能不断调用onTouchEvent方法
    }

    /**
     * 改变已选圆点的状态
     */
    private void changeMode(int i) {
        for (GestureRound round : list) {
            round.setState(i);
        }

    }

    /**
     * 获取直线的起始位置的XY值
     */
    private void getStartPosition(GestureRound round) {
        this.startX = (round.getLeft() + round.getRight()) / 2;
        this.startY = (round.getTop() + round.getBottom()) / 2;
    }

    /**
     * 还原状态
     */
    private void restoreState() {
        new Handler().postDelayed(new Runnable() {  //延迟还原状态
            public void run() {
                pathLine.reset(); //重置笔画
                moveX = 0;
                moveY = 0;
                for (int i = 0; i < COLUMN_NUMBER; i++) { //循环遍历二维数组,先遍历 列 貌似速度快一些
                    for (int j = 0; j < LINE_NUMBER; j++) {
                        GestureRound round = gestureViews[j][i];
                        if (list.contains(round)) { //判断当前的触摸点是否在圆点内
                            round.setState(0);
                        }
                    }
                }
                list.clear();
                invalidate();
                isClick = true;
            }
        }, gestureData.getDelay());
    }

    /**
     * 获取滑动时选择的圆点
     */
    private GestureRound getRound(float x, float y) {
        for (int i = 0; i < COLUMN_NUMBER; i++) { //循环遍历二维数组,先遍历 列 貌似速度快一些
            for (int j = 0; j < LINE_NUMBER; j++) {
                if (judgeRound(gestureViews[j][i], x, y)) { //判断当前的触摸点是否在圆点内
                    return gestureViews[j][i]; //返回圆点
                }
            }
        }
        return null;
    }

    /**
     * 判断当前的触摸点是否在圆点内
     */
    private boolean judgeRound(GestureRound gestureRound, float x, float y) {
        float DIFFER = gestureViewWidth * 0.2f;   //DIFFER是误差点
        if (x >= gestureRound.getLeft() + DIFFER && x <= gestureRound.getRight() - DIFFER
                && y >= gestureRound.getTop() + DIFFER && y <= gestureRound.getBottom() - DIFFER)
            return true;
        else
            return false;
    }

    /**
     * 设置是否二次选择
     */
    public void setAgain(boolean b) {
        this.isAgain = b;
    }

    public void setOnResultListener(OnResultListener listener) {
        this.resultListener = listener;
    }

    public interface OnResultListener {
        void Result(String result);
    }
}

像这些功能设置

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Gesture_Styles">
        <attr name="Gesture_Line_Number" format="integer"></attr><!--每行圆点的个数-->
        <attr name="Gesture_Column_Number" format="integer"></attr><!--每列圆点的个数-->
        <attr name="Gesture_BorderWidth" format="float"></attr> <!--边框大小-->
        <attr name="Gesture_FilletWidth" format="float"></attr> <!--内圆大小-->
        <attr name="Gesture_BetweenWidth" format="float"></attr> <!--每行之间的距离大小-->
        <attr name="Gesture_LineWidth" format="float"></attr> <!--直线的宽度大小-->
        <attr name="Gesture_Round_Number" format="integer"></attr><!--每次最少选中圆点个数-->
        <attr name="Gesture_isAgain" format="boolean"></attr><!--是否需要二次选择-->
        <attr name="Gesture_Delay" format="integer"></attr><!--延迟还原状态的时间,单位 秒-->

        <attr name="Gesture_DefaultBorderColor" format="color|reference"></attr> <!--默认边框颜色-->
        <attr name="Gesture_DefaultFilletColor" format="color|reference"></attr> <!--默认内圆颜色-->
        <attr name="Gesture_DefaultExcircleColor" format="color|reference"></attr> <!--默认外圆颜色-->


        <attr name="Gesture_SelectBorderColor" format="color|reference"></attr> <!--选择边框颜色-->
        <attr name="Gesture_SelectFilletColor" format="color|reference"></attr> <!--选择内圆颜色-->
        <attr name="Gesture_SelectExcircleColor" format="color|reference"></attr> <!--选择外圆颜色-->
        <attr name="Gesture_SelectLineColor" format="color|reference"></attr> <!--选择线条颜色-->

        <attr name="Gesture_ErrorBorderColor" format="color|reference"></attr> <!--错误边框颜色-->
        <attr name="Gesture_ErrorFilletColor" format="color|reference"></attr> <!--错误内圆颜色-->
        <attr name="Gesture_ErrorExcircleColor" format="color|reference"></attr> <!--错误外圆颜色-->
        <attr name="Gesture_ErrorLineColor" format="color|reference"></attr> <!--错误线条颜色-->

        <attr name="Gesture_CorrectBorderColor" format="color|reference"></attr> <!--正确边框颜色-->
        <attr name="Gesture_CorrectFilletColor" format="color|reference"></attr> <!--正确内圆颜色-->
        <attr name="Gesture_CorrectExcircleColor" format="color|reference"></attr> <!--正确外圆颜色-->
        <attr name="Gesture_CorrectLineColor" format="color|reference"></attr> <!--正确线条颜色-->

    </declare-styleable>
    <string name="BorderWidth" format="float" type="dimen">2</string><!--默认边框大小-->
    <string name="FilletWidth" format="float" type="dimen">0.3</string><!--默认内圆大小-->
    <string name="BetweenWidth" format="float" type="dimen">0.25</string><!--默认每行之间的距离大小-->
    <string name="LineWidth" format="float" type="dimen">0.3</string><!--默认直线的宽度大小-->
    <bool name="isAgain">true</bool>
    <integer name="Round_Number">4</integer><!--默认每次最少选中圆点个数-->
    <integer name="Delay">500</integer><!--延迟还原状态的时间,单位 秒-->
    <integer name="Line_Number">3</integer><!--每行圆点的个数-->
    <integer name="Column_Number">3</integer><!--每列圆点的个数-->
</resources>

全部在XML里面就可以设置,非常方便


感觉没漏下什么了大笑有缺少的,大家可以加上去,注释已经非常详细了,基本上看一眼就能知道是什么逻辑,什么功能,自己修改也很方便


最后 这是代码下载,已经编译过了,可以直接在build-->outputs里面找到APK先安装看一下效果: 

http://download.csdn.net/detail/yanmantian/9716762

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值