这几天有空,自己试着写了一个手势锁,基本实现了手势锁的功能,目前实现的只是3X3的矩阵的锁,等有空了再来继续实现可以动态设置矩阵个数和通过自定义属性实现圆环、线段的颜色,尺寸等。先看一下效果:
使用方法:
<com.leilu.lock.GestureLockView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" >
</com.leilu.lock.GestureLockView>
对应的代码,里面有非常详细的注释:
package com.leilu.lock;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
/**
* 手势锁
*
* @author Administrator
*
*/
public class GestureLockView extends View {
private static final int MIN_SELECTED_CYCLES = 4;// 需要选择的最小的圆形的个数
private static final int STATE_NORMAL = 0;// 圆形正常状态
private static final int STATE_ERROR = 1;// 圆形错误状态
private static final int STATE_SELECTED = 2;// 圆形的已经选择状态
private static final int STATE_OK = 3;// 圆形的成功状态
private Cycle[][] cycles = new Cycle[3][3];// 圆形的数组,是一个3X3的矩阵
private List<Cycle> cycleList = new ArrayList<Cycle>();// 保存选择圆形的集合
private int cycleR;// 圆形半径
private int cycleCenterR;// 圆形的圆形的半径
private int width, height;// 控件的宽度和高度
private int rowGap;// 行间距
private int rectWidth;// 整个圆形组成的举行的宽度,由于这个矩阵我们设置成正方形,所以宽高一样,这里只需要记录宽度即可
private boolean isSelect;// 是否选择了圆形
private float moveX, moveY;// 移动的时候手机的坐标点的x和y坐标
private boolean isMoving;// 是否正在移动手指
private int currentState = STATE_NORMAL;// 当前的状态,默认为正常状态
private Paint cyclePaint;// 花园的画笔
private Paint cycleCenterPaint;// 圆的中心画笔,用于画每个圆的中心点
private Paint linePaint;// 画线段的画笔
public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public GestureLockView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 计算圆形组成的矩形的宽度
rectWidth = 2 * rowGap + 3 * 2 * cycleR;
int width = measure(widthMeasureSpec);
int height = measure(heightMeasureSpec);
int tempWidth = width;
if (width > height) {
width = height;
height = tempWidth;
}
setMeasuredDimension(width, height);
}
/**
* 测量对应的尺寸
*
* @param measureSpec
* @return
*/
private int measure(int measureSpec) {
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = Math.max(size, rectWidth);
} else {
result = rectWidth;
}
return result;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 获取到控件的高度和宽度
width = getWidth();
height = getHeight();
Toast.makeText(getContext(), width + " " + height,
Toast.LENGTH_SHORT).show();
// 计算圆形组成的矩形的宽度
rectWidth = 2 * rowGap + 3 * 2 * cycleR;
// 初始化圆形数组坐标
initcycles();
}
public GestureLockView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
cycleR = dp2px(40);// 给圆形半径负值
cycleCenterR = dp2px(15);// 给圆形的圆心半径赋值
rowGap = cycleR / 2;// 设置行间距为圆形的半径的一半
// 初始化画圆的画笔
cyclePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 初始化画线的画笔
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 为了使线段的宽度和圆心直径对其,这里设置线段画笔的宽度和圆心的直径
linePaint.setStrokeWidth(2 * cycleCenterR);
linePaint.setColor(Color.BLUE);
// 初始化画每个圆圆心点画笔
cycleCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
cycleCenterPaint.setColor(Color.BLUE);
}
/**
* 初始化圆形数组
*/
private void initcycles() {
// 计算第一个圆的圆心x坐标
int firstX = (width - rectWidth) / 2 + cycleR;
// 计算第一个圆的圆心y坐标
int firstY = (height - rectWidth) / 2 + cycleR;
// 计算第一个圆和第二个圆圆心的距离
int distance = 2 * cycleR + rowGap;
// 第一行的圆心
cycles[0][0] = new Cycle(firstX, firstY);
cycles[0][1] = new Cycle(firstX + distance, firstY);
cycles[0][2] = new Cycle(firstX + 2 * distance, firstY);
// 第二行的圆心
cycles[1][0] = new Cycle(firstX, firstY + distance);
cycles[1][1] = new Cycle(firstX + distance, firstY + distance);
cycles[1][2] = new Cycle(firstX + 2 * distance, firstY + distance);
// 第三行的圆心
cycles[2][0] = new Cycle(firstX, firstY + 2 * distance);
cycles[2][1] = new Cycle(firstX + distance, firstY + 2 * distance);
cycles[2][2] = new Cycle(firstX + 2 * distance, firstY + 2 * distance);
}
/**
* 将dp的值转换成px
*
* @param value
* @return
*/
private int dp2px(int value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
value, getContext().getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
// 如果已经选择了圆形,则画他们的连接线
if (isSelect) {
// 先设置连接线的画笔的状态的颜色
setLineStateColor();
// 画已经选择的圆之间的连接线
drawLines(canvas);
// 如果手指正在移动,则画正在移动的连接线
if (isMoving) {
// 获取到最后的选择的圆
Cycle cycle = cycleList.get(cycleList.size() - 1);
canvas.drawLine(cycle.x, cycle.y, moveX, moveY, linePaint);
}
} // 画圆形
drawCycles(canvas);
}
/**
* 画已经选择的圆之间的连接线
*
* @param canvas
*/
private void drawLines(Canvas canvas) {
int size = cycleList.size();
// 如果选择的圆形个数小于两个,则不画线段
if (size < 2) {
return;
}
Cycle lastCycle = null;
for (int i = 0; i < size; i++) {
Cycle startCycle = cycleList.get(i);
int nextIndex = i + 1;
if (nextIndex >= size - 1) {
nextIndex = size - 1;
}
Cycle endCycle = cycleList.get(nextIndex);
if (lastCycle == null) {
canvas.drawLine(startCycle.x, startCycle.y, endCycle.x,
endCycle.y, linePaint);
} else {
canvas.drawLine(lastCycle.x, lastCycle.y, endCycle.x,
endCycle.y, linePaint);
}
lastCycle = endCycle;
}
}
/**
* 设置画线的画笔对应状态的颜色
*/
private void setLineStateColor() {
if (currentState == STATE_OK) {
linePaint.setColor(Color.MAGENTA);
} else if (currentState == STATE_ERROR) {
linePaint.setColor(Color.RED);
} else if (currentState == STATE_NORMAL) {
linePaint.setColor(Color.BLUE);
}
}
/**
* 画对应圆的圆心
*
* @param canvas
* @param cycle
* 对应的圆
*/
private void drawCycleCenters(Canvas canvas, Cycle cycle) {
canvas.drawCircle(cycle.x, cycle.y, cycleCenterR, cycleCenterPaint);
}
/**
* 画圆形
*
* @param canvas
*/
private void drawCycles(Canvas canvas) {
int rows = cycles.length;
int colunms = cycles[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < colunms; j++) {
Cycle cycle = cycles[i][j];
//
if (currentState == STATE_NORMAL) {
setCycleStateColor(cycle.state);
} else if (currentState == STATE_OK
|| currentState == STATE_ERROR) {
setCycleStateColor(currentState);
}
// 画圆
canvas.drawCircle(cycle.x, cycle.y, cycleR, cyclePaint);
// 画这个圆对应的圆心
drawCycleCenters(canvas, cycle);
}
}
}
/**
* 根据状态将画笔设置成对应的颜色
*
* @param paint
* @param state
*/
private void setCycleStateColor(int state) {
if (state == STATE_NORMAL) {
cyclePaint.setColor(Color.GREEN);
} else if (state == STATE_ERROR) {
cyclePaint.setColor(Color.RED);
} else if (state == STATE_SELECTED) {
cyclePaint.setColor(Color.CYAN);
} else if (state == STATE_OK) {
cyclePaint.setColor(Color.MAGENTA);
}
}
/**
* 圆形的实体类,封装了x,y两个参数,分别代表x坐标和y坐标
*
* @author Administrator
*
*/
private class Cycle {
public float x;// x坐标
public float y;// y坐标
public int state = STATE_NORMAL;// 当前状态,默认为普通状态
public Cycle(float x, float y) {
this.x = x;
this.y = y;
}
/**
* 此处需要重写equas方法,因为会调用
* list集合的containse方法,是否存在这个圆,这里根据x和y来判断,如果x和y都相等就表示他们的值相等
*
* @param o
* @return true,是一个对象;false,不是同一个对象
*/
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof Cycle)) {
return false;
}
Cycle cycle = (Cycle) o;
if (cycle.x == x && cycle.y == y) {
return true;
}
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 将当前状态重置为正常状态
currentState = STATE_NORMAL;
// 按下的时候将已经选择的圆形的集合清空,以免重复绘制上一次的选择的路径
clearCycleList();
moveX = event.getX();
moveY = event.getY();
// 检查点击的这个点是否在圆形内,如果在则将选择设置为true
Cycle cycle = checkBounds(moveX, moveY);
if (cycle != null) {
isSelect = true;
// 将这个点添加进集合
cycle.state = STATE_SELECTED;
cycleList.add(cycle);
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
// 检查点击的这个点是否在圆形内,如果在某个圆形区域内,则去判断是否在已经选择的圆形集合中是否已经包含了这个圆
cycle = checkBounds(moveX, moveY);
if (cycle != null) {
// 如果这个圆没有被选择过,则将该圆添加进集合
if (!checkHasCycle(cycle)) {
cycleList.add(cycle);
}
} else {
// 如果这个移动的点没有在圆形区域,为了保证能够正常画上一个圆到手指的连接线,需要将状态设置为true
// 这个状态用于画图的时候判断是否需要画这个动态的链接先,如果是false则不画
isMoving = true;
}
invalidate();
break;
case MotionEvent.ACTION_UP:
// 将正在移动手指的状态设置为false,此时如果重绘,则不会画最后选择的一个圆形到手指一动点的连接线
isMoving = false;
// 判断选择的圆形的个数是否合法
if (cycleList.size() == 0 || cycleList.size() == 1) {
// 如果选择的圆为0个或者1个,则不绘制路径和这个圆,清空集合
clearCycleList();
isSelect = false;
} else if (cycleList.size() >= 1
&& cycleList.size() < MIN_SELECTED_CYCLES) {
// 如果选择的圆的个数小于4个,则说明手势太简单,将其重置为错误状态
currentState = STATE_ERROR;
} else {
// 如果选择的圆符合个数,则将状态重置为成功状态
currentState = STATE_OK;
}
invalidate();
break;
}
return true;
}
/**
* 清空已经选择的圆形的集合
*/
private void clearCycleList() {
int rows = cycles.length;
int colunms = cycles[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < colunms; j++) {
Cycle cycle = cycles[i][j];
cycle.state = STATE_NORMAL;
}
}
cycleList.clear();
}
/**
* 判断已经选择的圆形集合里面是否包含了某个圆
*
* @param cycle
* @return 返回true,存在;返回false,不存在
*/
private boolean checkHasCycle(Cycle cycle) {
return cycleList.contains(cycle);
}
/**
* 检查坐标是否在某个圆的区域里面
*
* @param x
* x坐标
* @param y
* y坐标
* @return 不是空,在圆形某个圆形的区域;是空,不在某个圆形的区域
*/
private Cycle checkBounds(float x, float y) {
int rows = cycles.length;
int colunms = cycles[0].length;
Cycle cycle = null;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < colunms; j++) {
cycle = cycles[i][j];
float xDistance = cycle.x - x;
float yDistance = cycle.y - y;
if (Math.sqrt(xDistance * xDistance + yDistance * yDistance) <= cycleR) {
return cycle;
}
}
}
return null;
}
}