图案解锁

图案解锁实现步骤
1.自定义一个View

package com.example.administrator.mydemo.graphiclock;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


import com.example.administrator.mydemo.R;

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

/**
 * Created by Jack on 16/10/12.
 */
public class GraphicLockView extends View {

    private Point[][] points = new Point[3][3];  //创建一个3行3列的点数组

    private boolean isInit;   //判断有没有初始化

    private boolean isSelect;  //判断手指第一次按下屏幕有没有选中点

    private boolean isFinishMove;   //表示一次完整的图案绘制是否结束

    private boolean isMoveButNotPoint;   //表示手指在移动,但是并不是九宫格中的点

    private float width, height;   //屏幕宽高

    private static final int MIN_POINT = 4;    //最小能构成密码的点数

    private float offsetsX, offsetsY; //偏移量(在这里偏移量等于大边减去小边再除以2)

    private float bitmapR;   //图片资源的半径

    private float moveX, moveY;  //手势移动的x,y坐标

    private Bitmap bpPointNormal, bpPointPressed, bpPointError;  //点的三种图片

    private Bitmap bpLinePressed, bpLineError;  //线的三种图片

    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private List<Point> selectPointList = new ArrayList<>();   //储存按下的点的集合

    private Matrix matrix = new Matrix();  //矩阵,用来处理线的缩放

    private OnGraphicLockListener onGraphicLockListener;   //对外的监听器

    public GraphicLockView(Context context) {
        super(context);
    }

    public GraphicLockView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GraphicLockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setOnGraphicLockListener(OnGraphicLockListener onGraphicLockListener) {
        this.onGraphicLockListener = onGraphicLockListener;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制之前要先初始化一下点,所以要先判断有没有初始化过
        if (!isInit) {
            //初始化点
            initPoints();
        }
        //绘制——将点绘制到画布上
        pointToCanvas(canvas);

        if (selectPointList.size() > 0) {
            Point startPoint = selectPointList.get(0);
            //绘制九宫格坐标里的点
            for (int i = 0; i < selectPointList.size(); i++) {
                Point endPoint = selectPointList.get(i);
                lineToCanvas(canvas, startPoint, endPoint);
                startPoint = endPoint;
            }
            //绘制九宫格坐标以外的点
            if (isMoveButNotPoint) {
                lineToCanvas(canvas, startPoint, new Point(moveX, moveY));
            }
        }
    }

    /**
     * 初始化点
     */
    private void initPoints() {
        //1、先拿到画布的宽高(屏幕的宽高)
        width = getWidth();
        height = getHeight();

        /*================================================================================*/

        //2、判断横竖屏并且计算偏移量
        if (width > height) {   //横屏
            //横屏时只有x坐标有偏移量
            offsetsX = (width - height) / 2;
            /**
             * 将手机屏幕可以看作是一个正方形(因为九宫格是正方形,在这里比较好计算),以最小边为基准
             */
            width = height;
        } else {   //竖屏
            //竖屏时只有y坐标有偏移量
            offsetsY = ((height - width) / 3);
            height = width;
        }

        /*================================================================================*/

        //3、图片资源(图片资源自己加上)
        bpPointNormal = BitmapFactory.decodeResource(getResources(), R.drawable.point_normal);//默认图片
        bpPointPressed = BitmapFactory.decodeResource(getResources(), R.drawable.point_pressed);//选中图片
        bpPointError = BitmapFactory.decodeResource(getResources(), R.drawable.point_error);//报红提示图片
        bpLinePressed = BitmapFactory.decodeResource(getResources(), R.drawable.line_pressed);//连接线图片
        bpLineError = BitmapFactory.decodeResource(getResources(), R.drawable.line_error);//报红连接线

        /*================================================================================*/

        //4、点的坐标
        //第一排
        points[0][0] = new Point(offsetsX + width / 4, offsetsY + height / 4);
        points[0][1] = new Point(offsetsX + width / 2, offsetsY + height / 4);
        points[0][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 4);

        //第二排
        points[1][0] = new Point(offsetsX + width / 4, offsetsY + height / 2);
        points[1][1] = new Point(offsetsX + width / 2, offsetsY + height / 2);
        points[1][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 2);

        //第三排
        points[2][0] = new Point(offsetsX + width / 4, offsetsY + height - height / 4);
        points[2][1] = new Point(offsetsX + width / 2, offsetsY + height - height / 4);
        points[2][2] = new Point(offsetsX + width - width / 4, offsetsY + height - height / 4);


        /*================================================================================*/

        //5、计算图片资源的半径
        bitmapR = bpPointNormal.getWidth() / 2;

        /*================================================================================*/

        //6、设置密码按键,初始化每个点,设置为1——9
        int index = 1;
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                points[i][j].index = index;
                index++;
            }
        }
        /*================================================================================*/

        //初始化完成
        isInit = true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        moveX = event.getX();
        moveY = event.getY();
        isFinishMove = false;
        isMoveButNotPoint = false;
        Point point = null;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //每次手指按下的时候都表示重新绘制图案
                resetPoint();
                //1、检查当前按下的点与九宫格中的九个点是否吻合
                point = checkSelectPoint();
                if (point != null) {
                    isSelect = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isSelect) {
                    point = checkSelectPoint();
                    if (point == null) {
                        isMoveButNotPoint = true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isFinishMove = true;
                isSelect = false;
                break;
        }
        //选中重复检查
        if (!isFinishMove && isSelect && point != null) {
            if (checkCrossPoint(point)) {  //交叉点
                isMoveButNotPoint = true;
            } else {   //非交叉点(新的点)
                point.status = Point.STATE_PRESSED;
                selectPointList.add(point);
            }
        }
        //绘制结束
        if (isFinishMove) {
            //绘制不成立
            if (selectPointList.size() == 1) {
                resetPoint();
                //绘制错误,点不够
            } else if (selectPointList.size() < MIN_POINT && selectPointList.size() > 0) {
                if (null != onGraphicLockListener) {
                    onGraphicLockListener.setPwdFailure();
                }
                errorPoint();
                //绘制成功
            } else {
                if (null != onGraphicLockListener) {
                    String strPassword = "";
                    for (Point pwdPoint : selectPointList) {
                        strPassword += pwdPoint.index;
                    }
                    if (!TextUtils.isEmpty(strPassword)) {
                        onGraphicLockListener.setPwdSuccess(strPassword);
                    }
                    correctPoint();
                }
            }
        }
        //刷新view,会调用onDraw方法
        postInvalidate();
        return true;
    }

    /**
     * 检查交叉点
     *
     * @param point 点
     * @return 是否交叉
     */
    private boolean checkCrossPoint(Point point) {
        if (selectPointList.contains(point)) {
            return true;
        }
        return false;
    }

    /**
     * 设置绘制不成立
     */
    public void resetPoint() {
        //将点的状态还原
        for (Point point : selectPointList) {
            point.status = Point.STATE_NORMAL;
        }
        selectPointList.clear();
    }

    /**
     * 设置绘制错误,将点的状态还原
     */
    public void errorPoint() {
        for (Point point : selectPointList) {
            point.status = Point.STATE_ERROR;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(0);
            }
        }).start();
    }

    /**
     * 设置绘制成功,将点的状态还原
     */
    private void correctPoint() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(0);
            }
        }).start();
    }

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            for (Point point : selectPointList) {
                point.status = Point.STATE_NORMAL;
            }
            selectPointList.clear();
            postInvalidate();
        }
    };

    private Point checkSelectPoint() {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point point = points[i][j];
                if (AppUtil.isCoincide(point.x, point.y, bitmapR, moveX, moveY)) {
                    return point;
                }
            }
        }
        return null;
    }

    /**
     * 将点绘制到画布上
     *
     * @param canvas 画布
     */
    private void pointToCanvas(Canvas canvas) {
        //遍历点的集合
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point point = points[i][j];
                if (points[i][j].status == Point.STATE_PRESSED) {
                    canvas.drawBitmap(bpPointPressed, point.x - bitmapR, point.y - bitmapR, mPaint);
                } else if (points[i][j].status == Point.STATE_ERROR) {
                    canvas.drawBitmap(bpPointError, point.x - bitmapR, point.y - bitmapR, mPaint);
                } else {
                    canvas.drawBitmap(bpPointNormal, point.x - bitmapR, point.y - bitmapR, mPaint);
                }
            }
        }
    }

    /**
     * 将线绘制到画布上
     *
     * @param canvas     画布
     * @param startPoint 开始的点
     * @param endPoint   结束的点
     */
    private void lineToCanvas(Canvas canvas, Point startPoint, Point endPoint) {
        float lineLength = (float) AppUtil.twoPointDistance(startPoint, endPoint);
        float degree = AppUtil.getDegrees(startPoint, endPoint);
        canvas.rotate(degree, startPoint.x, startPoint.y);  //旋转
        if (startPoint.status == Point.STATE_PRESSED) {  //按下的状态
            //设置线的缩放比例,在这里线是往一个方向缩放的,即x轴,我们只需要设置x轴的缩放比例即可,y轴默认为1
            matrix.setScale(lineLength / bpLinePressed.getWidth(), (float) 0.2);
            matrix.postTranslate(startPoint.x - bpLinePressed.getWidth() / 8, startPoint.y - bpLinePressed.getHeight() / 8);
            canvas.drawBitmap(bpLinePressed, matrix, mPaint);
        } else {   //错误的状态
            matrix.setScale(lineLength / bpLineError.getWidth(), (float) 0.2);
            matrix.postTranslate(startPoint.x - bpLineError.getWidth() / 8, startPoint.y - bpLineError.getHeight() / 8);
            canvas.drawBitmap(bpLineError, matrix, mPaint);
        }
        canvas.rotate(-degree, startPoint.x, startPoint.y);  //把旋转的角度转回来
    }

    /**
     * 图案监听器
     */
    public interface OnGraphicLockListener {
        void setPwdSuccess(String password);

        void setPwdFailure();
    }
}

2.绘制点工具类

package com.example.administrator.mydemo.graphiclock;

/**
 * 自定义的Point
 * Created by Jack on 16/10/12.
 */
public class Point {

    //正常状态
    public static int STATE_NORMAL = 0;
    //选中状态
    public static int STATE_PRESSED = 1;
    //错误状态
    public static int STATE_ERROR = 2;

    public float x, y;

    public int index = 0, status = 0;

    public Point() {

    }

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }
}

3.滑动工具类

package com.example.administrator.mydemo.graphiclock;

/**
 * Created by Jack on 16/10/12.
 */
public class AppUtil {
    /**
     * 两点之间的距离
     *
     * @param a 第一个点
     * @param b 第二个点
     * @return 距离
     */
    public static double twoPointDistance(Point a, Point b) {
        //x轴差的平方加上y轴的平方,然后取平方根
        return Math.sqrt(Math.abs(a.x - b.x) * Math.abs(a.x - b.x) + Math.abs(a.y - b.y) * Math.abs(a.y - b.y));
    }

    /**
     * 判断手指移动的坐标与点的坐标是否重合(默认小于点半径的一半的时候表示重合)
     *
     * @param pointX 点横坐标
     * @param pointY 点纵坐标
     * @param r      点半径
     * @param moveX  手指移动的横坐标
     * @param moveY  手指移动的纵坐标
     * @return
     */
    public static boolean isCoincide(float pointX, float pointY, float r, float moveX, float moveY) {
        return Math.sqrt((pointX - moveX) * (pointX - moveX) + (pointY - moveY) * (pointY - moveY)) < r;
    }

    /**
     * 获取角度
     *
     * @param pointA 第一个点
     * @param pointB 第二个点
     * @return
     */
    public static float getDegrees(Point pointA, Point pointB) {
        return (float) Math.toDegrees(Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x));
    }
}

4.调用逻辑

package com.example.administrator.mydemo.graphiclock;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;

import com.example.administrator.mydemo.R;


public class GraphicLockActivity extends AppCompatActivity implements GraphicLockView.OnGraphicLockListener {

    private TextView mTvIno;
    private GraphicLockView mGlGraphicLockView;
    private boolean isFirstSetPwd;
    private String mPassword;   //记录第一次绘制的密码
    private int type = 0;   //判断时登录还是设置密码

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_graphic_lock);

        initView();
    }

    private void initView() {
        mTvIno = (TextView) findViewById(R.id.agl_tv_info);
        mGlGraphicLockView = (GraphicLockView) findViewById(R.id.agl_gl_lock);
        isFirstSetPwd = true;
        mTvIno.setText("请绘制解锁图案");

        mGlGraphicLockView.setOnGraphicLockListener(this);
    }

    @Override
    public void setPwdSuccess(String password) {
        if (type == 0) {   //设置密码
            if (isFirstSetPwd) {
                mTvIno.setText("请再次绘制图案进行确认");
                mPassword = password;
                isFirstSetPwd = false;
            } else {
                if (mPassword.equals(password)) {
                    Toast.makeText(this, "设置密码成功", Toast.LENGTH_SHORT).show();
                    mTvIno.setText("你可以登录了");
                    type = 1;
                } else {
                    Toast.makeText(this, "两次设置的密码不一致,请重试", Toast.LENGTH_SHORT).show();
                }
            }
        } else if (type == 1) {  //登录
            if (mPassword.equals(password)) {
                Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "密码错误,登录失败", Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isFirstSetPwd = true;
        mPassword = "";
    }

    @Override
    public void setPwdFailure() {
        if (type == 0) {
            mTvIno.setText("请至少连接四个点");
            Toast.makeText(this, "密码过短,设置失败", Toast.LENGTH_SHORT).show();
        } else if (type == 1) {
            mTvIno.setText("你可以登录了");
            Toast.makeText(this, "密码错误,登录失败", Toast.LENGTH_SHORT).show();
        }
    }
}

5.布局引用

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/agl_tv_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:textSize="18dp" />

    <TextView
        android:id="@+id/agl_tv_forget"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/agl_tv_info"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="5dp"
        android:text="忘记图案密码"
        android:visibility="gone" />

    <com.example.administrator.mydemo.graphiclock.GraphicLockView
        android:id="@+id/agl_gl_lock"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/agl_tv_forget" />

</RelativeLayout>

完成

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值