package com.bigfat.lockpattern.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.bigfat.lockpattern.model.Point;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:fbzhh007@gmail.com">bigfat</a>
* @since 2015/3/3
*/
public class LockPatternView extends View {
private static final String TAG = "LockPatternView";
public interface OnPatternChangeListener {
void onPatternChange(String password);
}
/**
* 密码选择结果回调接口
*/
private OnPatternChangeListener mOnPatternChangeListener;
/**
* 画笔
*/
private Paint mPaint;
/**
* 点阵
*/
private Point[][] points = new Point[3][3];
/**
* 按下的点的集合
*/
private List<Point> mSelectedPoints = new ArrayList<>();
/**
* 是否初始化
*/
private boolean mIsInit;
/**
* 解锁区域正方形的边长
*/
private int mWidth;
/**
* 点阵的X轴偏移量
*/
private int offsetX;
/**
* 点阵的Y轴偏移量
*/
private int offsetY;
/**
* 点的半径
*/
private int mPointRadius;
/**
* 触摸按下时是否直接按在一个点上,如果不是的话,将不进行之后移动的判断
*/
private boolean mIsFirstMoveDownOnPoint;
/**
* 触摸移动的x轴坐标
*/
private int movingX;
/**
* 触摸移动的y轴坐标
*/
private int movingY;
/**
* 是否触摸时没有触摸到点
*/
private boolean mIsMovingNoPoint;
public LockPatternView(Context context) {
super(context);
}
public LockPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LockPatternView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setmOnPatternChangeListener(OnPatternChangeListener mOnPatternChangeListener) {
this.mOnPatternChangeListener = mOnPatternChangeListener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mIsMovingNoPoint = false;
movingX = (int) event.getX();
movingY = (int) event.getY();
Point point = null;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //移动开始
resetPoints();
postInvalidate();
point = checkSelectPoint(movingX, movingY);
if (point != null) {
mIsFirstMoveDownOnPoint = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsFirstMoveDownOnPoint) {
point = checkSelectPoint(movingX, movingY);
if (point == null) {
mIsMovingNoPoint = true;
}
}
break;
case MotionEvent.ACTION_UP: //移动结束
StringBuilder password = new StringBuilder();
if (mSelectedPoints.size() < 5) {//5个点以内长度不足,使用错误绘制
setSelectedPointError();
} else {
for (Point p : mSelectedPoints) {
password.append(String.valueOf(p.getIndex()));
}
}
if (mOnPatternChangeListener != null) {
mOnPatternChangeListener.onPatternChange(password.toString());
}
mIsFirstMoveDownOnPoint = false;
break;
}
//重复选中检查
if (mIsFirstMoveDownOnPoint && point != null) {
if (!isPointSelected(point)) {//没有被选中过的点,添加到选中队列中
point.setState(Point.Status.PRESSED);
mSelectedPoints.add(point);
} else {//已经被选中过的点,当做没有点来处理
mIsMovingNoPoint = true;
}
}
//重绘View
postInvalidate();
return true;
}
/**
* 重置点阵状态
*/
private void resetPoints() {
for (Point point : mSelectedPoints) {
point.setState(Point.Status.NORMAL);
}
mSelectedPoints.clear();
}
/**
* 判断点是否已被选中过
*/
private boolean isPointSelected(Point point) {
return mSelectedPoints.contains(point);
}
/**
* 将已选点设置为错误状态
*/
private void setSelectedPointError() {
for (Point point : mSelectedPoints) {
point.setState(Point.Status.ERROR);
}
}
/**
* 获取触摸的点
*
* @param x 触摸的x坐标
* @param y 触摸的y坐标
* @return 触摸位置的点
*/
private Point checkSelectPoint(float x, float y) {
for (Point[] pointArray : points) {
for (Point point : pointArray) {
if (point.with(mPointRadius, x, y)) {
return point;
}
}
}
return null;
}
@Override
protected void onDraw(Canvas canvas) {
if (!mIsInit) {
initPaint();
initPoints();
}
points2Canvas(canvas);
if (mSelectedPoints.size() > 0) {
for (int i = 1; i < mSelectedPoints.size(); i++) {
lines2Canvas(canvas, mSelectedPoints.get(i - 1), mSelectedPoints.get(i));
}
if (mIsMovingNoPoint) {
lines2Canvas(canvas, mSelectedPoints.get(mSelectedPoints.size() - 1), new Point(movingX, movingY));
}
}
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(10);
}
/**
* 初始化点
*/
private void initPoints() {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
mWidth = Math.min(width, height);
mPointRadius = mWidth / 12;
if (width > height) {
offsetX = (width - height) / 2;
} else {
offsetY = (height - width) / 2;
}
// Log.i(TAG, "width:" + width + "height:" + height + "offsetX:" + offsetX + "offsetY:" + offsetY);
//初始化点
int period = mWidth / 4;
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
points[x][y] = new Point(offsetX + period * (y + 1), offsetY + period * (x + 1));
points[x][y].setIndex(x * 3 + y);
}
}
mIsInit = true;
}
/**
* 将点绘制到画布上
*/
private void points2Canvas(Canvas canvas) {
for (Point[] pointArray : points) {
for (Point point : pointArray) {
switch (point.getState()) {
case NORMAL:
mPaint.setColor(0xff0000ff);
canvas.drawCircle(point.getX(), point.getY(), mPointRadius, mPaint);
break;
case PRESSED:
mPaint.setColor(0xff00ff00);
canvas.drawCircle(point.getX(), point.getY(), mPointRadius, mPaint);
break;
case ERROR:
mPaint.setColor(0xffff0000);
canvas.drawCircle(point.getX(), point.getY(), mPointRadius, mPaint);
break;
}
}
}
}
/**
* 画线
*
* @param canvas 画布
* @param pointA 点A
* @param pointB 点B
*/
private void lines2Canvas(Canvas canvas, Point pointA, Point pointB) {
switch (pointA.getState()) {
case PRESSED:
mPaint.setColor(0xff00ff00);
canvas.drawLine(pointA.getX(), pointA.getY(), pointB.getX(), pointB.getY(), mPaint);
break;
case ERROR:
mPaint.setColor(0xffff0000);
canvas.drawLine(pointA.getX(), pointA.getY(), pointB.getX(), pointB.getY(), mPaint);
break;
}
}
}