/**
* 手势解锁
*/
public class GestureLockView extends View implements GestureLockViewController {
/**
* 控件整体宽度、高度
*/
private float width = 0;
private float height = 0;
//是否出初始化相关基本数值
private boolean isBasic = false;
//九宫格的行列数目
private static final int ROW_NUM = 3;
//九宫格的间距数目
private static final int SPACE_NUM = ROW_NUM + 1;
//九宫格的圆心坐标
private Point[][] mPoints = new Point[ROW_NUM][ROW_NUM];
//画笔
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* 一对宫格之间的间距(圆的大小会根据设置的间距动态适配)
* 3个格子会导致:总共有4个间距
*/
private float mCircleSpacing;
/**
* 最外圈
*/
//默认:外圈直径占整个控件宽度的比例: 3.1.1.1当外圈间距没有自定义设置时使用
private static final float DEFAULT_RATE_OUTER_RING_WIDTH_IN_VIEW = (float) 1/6;
//最外圈直径
private float mOuterRingWidth = 120;
//最外圈半径
private float mRadius;
/**
* 半透明圆
*/
//默认:中间圈直径与最小圈直径的比例:3.1.3 初始化:中间圆的直径
private static final float DEFAULT_RATE_INNER_BACKGROUND_RING_WIDTH_IN_INNER_RING = 1.3f;
//内圆背景直径(半透明内圆)
private float mInnerBackgroudWidth;
//小圆半透明背景半径
private float mInnerBackgroudRadius;
/**
* 最小圆
*/
//默认:最小圈直径与外圆直径的比例:3.1.2 初始化:最小圈直径
private static final float DEFAULT_RATE_INNER_RING_WIDTH_IN_OUT_RING = (float) 1/3;
//最小的圆的直径
private float mInnerRingWidth = mOuterRingWidth * DEFAULT_RATE_INNER_RING_WIDTH_IN_OUT_RING;
//小圆半径
private float mInnerRingRadius;
/**
* 三角形边长
*/
//默认:三角形边长与最外圆半径的比例:3.1
private static final float DEFAULT_RATE_ARROW_LENGTH_IN_OUTER_RING = 0.25f;
private float mArrowLength;
/**
* 圆心与边界的间距:外圈间距 + 外圈半径
*/
float roundW;
//普通状态下圈的颜色
private int mColorUpRing = 0xFF378FC9;
//按下时圈的颜色
private int mColorOnRing = 0xFF378FC9;
//失败状态下圈的颜色
private int mColorErrorRing = 0xFF378FC9;
//未按下时圆圈的边宽
private int mNoFingerStrokeWidth = 2;
//按下时圆圈的边宽
private int mOnStrokeWidth = 4;
public GestureLockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.GestureLock_styleable, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.GestureLock_styleable_color_on_ring) {
mColorOnRing = a.getColor(attr, mColorOnRing);
} else if (attr == R.styleable.GestureLock_styleable_color_up_ring) {
mColorUpRing = a.getColor(attr, mColorUpRing);
} else if (attr == R.styleable.GestureLock_styleable_color_error_ring) {
mColorErrorRing = a.getColor(attr, mColorErrorRing);
} else if (attr == R.styleable.GestureLock_styleable_inner_ring_width) {
mInnerRingWidth = a.getDimensionPixelSize(attr, 0);
} else if (attr == R.styleable.GestureLock_styleable_outer_ring_spacing_width) {
mCircleSpacing = a.getDimensionPixelSize(attr, 0);
} else if (attr == R.styleable.GestureLock_styleable_inner_background_width) {
mInnerBackgroudWidth = a.getDimensionPixelSize(attr, 0);
}
}
a.recycle();
setClickable(true);
}
/**
* 一、测量过程,默认400dp
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MathUtil.measure(widthMeasureSpec);
height = MathUtil.measure(heightMeasureSpec);
width = Math.min(width, height);
height = width;
setMeasuredDimension((int)width, (int)height);
}
/**
* 二、绘制过程
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
if (!isBasic) {
initBasic();
}
//绘制圆以及显示当前状态
drawToCanvas(canvas);
}
/**
* 三、初始化Cache信息
* 1. 初始化外圆、中间圆、内圆的半径直径 和 三角形的边长
* 2. 初始化圆心坐标
*/
private void initBasic() {
float y = 0;
initGestureLockViewWidth();
// 计算圆圈的大小及位置
roundW = width - (mOuterRingWidth * ROW_NUM);
roundW = roundW / 4 + mOuterRingWidth / 2;
mPoints[0][0] = new Point(getCircleCentreXByRow(0), y + roundW);
mPoints[0][1] = new Point(getCircleCentreXByRow(1), y + roundW);
mPoints[0][2] = new Point(getCircleCentreXByRow(2), y + roundW);
mPoints[1][0] = new Point(getCircleCentreXByRow(0), y + height / 2);
mPoints[1][1] = new Point(getCircleCentreXByRow(1), y + height / 2);
mPoints[1][2] = new Point(getCircleCentreXByRow(2), y + height / 2);
mPoints[2][0] = new Point(getCircleCentreXByRow(0), y + height - roundW);
mPoints[2][1] = new Point(getCircleCentreXByRow(1), y + height - roundW);
mPoints[2][2] = new Point(getCircleCentreXByRow(2), y + height - roundW);
int k = 0;
for (Point[] ps : mPoints) {
for (Point p : ps) {
p.index = k;
k++;
}
}
isBasic = true;
}
//3.1 初始化外圆、中间圆、内圆的半径直径 和 三角形的边长
private void initGestureLockViewWidth() {
// 3.1.1 初始化:外圈直径 和 外圈之间的间距
if (mCircleSpacing == 0) {
//3.1.1.1 当外圈间距没有自定义设置时:外圈直径为整体的 1/6; 间距为3个外圈去除后的结余的1/4 (有四个间距)
initCircleSpacing();
} else {
//3.1.1.2 当外圈间距自定义设置时:外圈直径为 (整体 - 4个外圈间距)/3
float mSpacing = mCircleSpacing * SPACE_NUM;
mOuterRingWidth = (width - mSpacing) / ROW_NUM;
}
//防止手动设置圆圆之间间距过大问题:3.1.1.2中可能出现间距设置过大,导致mOuterRingWidth外圈半径为负值
if (mOuterRingWidth <= 0) {
initCircleSpacing();
}
//3.1.2 初始化:最小圈直径
if (mInnerRingWidth == 0 || mInnerRingWidth >= mOuterRingWidth) {
//如果自定义设置的最小圈为0 或者 比最外圈还大,则重置为外圈的1/3
mInnerRingWidth = mOuterRingWidth * DEFAULT_RATE_INNER_RING_WIDTH_IN_OUT_RING;
}
//3.1.3 初始化:中间圆的直径
if (mInnerBackgroudWidth == 0 || mInnerBackgroudWidth >= mOuterRingWidth) {
//如果自定义设置的最小圈为0 或者 比最外圈还大,则重置为最小圈的1.3倍
mInnerBackgroudWidth = mInnerRingWidth * DEFAULT_RATE_INNER_BACKGROUND_RING_WIDTH_IN_INNER_RING;
}
mRadius = mOuterRingWidth / 2;
mInnerRingRadius = mInnerRingWidth / 2;
mInnerBackgroudRadius = mInnerBackgroudWidth / 2;
mArrowLength = mRadius * DEFAULT_RATE_ARROW_LENGTH_IN_OUTER_RING;//三角形的边长
}
//3.1.1.1 当外圈间距没有自定义设置时:初始化外圈直径 和 外圈之间的间距
private void initCircleSpacing() {
// 计算每个GestureLockView的宽度
mOuterRingWidth = width * DEFAULT_RATE_OUTER_RING_WIDTH_IN_VIEW;
//计算每个GestureLockView的间距
mCircleSpacing = (width - mOuterRingWidth * 3) / 4;
}
//根据列索引,获取圆心X轴坐标
public float getCircleCentreXByRow(int i) {
if (i == 0) {
return mCircleSpacing + mRadius;
} else if (i == 1) {
return width / 2;
}
return mCircleSpacing * 3 + mOuterRingWidth * 2 + mRadius;
}
/**
* 四、图像绘制
*
* @param canvas
*/
private void drawToCanvas(Canvas canvas) {
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);//如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置
// 4.1 画连线
drawAllLine(canvas);
// 42 画所有点
drawAllPoint(canvas);
// 4.3 是否绘制方向图标
if (isShow) {
drawDirectionArrow(canvas);
}
}
/**
* 4.1 绘制解锁连接线
*
* @param canvas
*/
private void drawAllLine(Canvas canvas) {
if (sPoints.size() > 0) {
Point tp = sPoints.get(0);
//4.1.1 已经确定的圆心连线
for (int i = 1; i < sPoints.size(); i++) {
//根据移动的方向绘制线
Point p = sPoints.get(i);
if (p.state == Point.STATE_CHECK_ERROR) {
drawErrorLine(canvas, tp, p);
} else {
drawLine(canvas, tp, p);
}
tp = p;
}
//4.1.2 最后一个圆心到当前移动位置的连线
if (this.movingNoPoint) {
//到达下一个点停止移动绘制固定的方向
drawLine(canvas, tp, new Point((int) moveingX + 20, (int) moveingY));
}
}
}
private void drawLine(Canvas canvas, Point a, Point b) {
mPaint.setColor(mColorOnRing);
mPaint.setStrokeWidth(3);
canvas.drawLine(a.x, a.y, b.x, b.y, mPaint);
}
private void drawErrorLine(Canvas canvas, Point a, Point b) {
mPaint.setColor(mColorErrorRing);
mPaint.setStrokeWidth(3);
canvas.drawLine(a.x, a.y, b.x, b.y, mPaint);
}
/**
* 4.2 绘制解锁图案所有的点
*
* @param canvas
*/
private void drawAllPoint(Canvas canvas) {
for (int i = 0; i < mPoints.length; i++) {
for (int j = 0; j < mPoints[i].length; j++) {
Point p = mPoints[i][j];
if (p != null) {
if (p.state == Point.STATE_CHECK) {
onDrawOn(canvas, p);
} else if (p.state == Point.STATE_CHECK_ERROR) {
onDrawError(canvas, p);
} else {
onDrawNoFinger(canvas, p);
}
}
}
}
}
//4.2.1 绘制普通状态
private void onDrawNoFinger(Canvas canvas, Point p) {
// 绘制外圆
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mColorUpRing);
mPaint.setStrokeWidth(mNoFingerStrokeWidth);
canvas.drawCircle(p.x, p.y, mRadius, mPaint);
}
//4.2.2 绘制按下时状态
private void onDrawOn(Canvas canvas, Point p) {
// 绘制白背景:填充
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(p.x, p.y, mRadius, mPaint);
// 绘制外圆的描边线:描边
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mColorOnRing);
mPaint.setStrokeWidth(mOnStrokeWidth);
canvas.drawCircle(p.x, p.y, mRadius, mPaint);
// 绘制内圆背景
onDrawInnerCircleBackground(canvas, p, mColorOnRing);
// 绘制内圆:填充
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mColorOnRing);
canvas.drawCircle(p.x, p.y, mInnerRingRadius, mPaint);
}
//绘制内圆透明背景
private void onDrawInnerCircleBackground(Canvas canvas, Point p, int color) {
// 绘制内圆
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(color);
mPaint.setAlpha(100);
canvas.drawCircle(p.x, p.y, mInnerBackgroudRadius, mPaint);
}
//4.2.3 绘制失败时时状态
private void onDrawError(Canvas canvas, Point p) {
// 绘制白背景:填充
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(p.x, p.y, mRadius, mPaint);
// 绘制外圆的描边线:描边
mPaint.setColor(mColorErrorRing);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mOnStrokeWidth);
canvas.drawCircle(p.x, p.y, mRadius, mPaint);
// 绘制内圆背景
onDrawInnerCircleBackground(canvas, p, mColorErrorRing);
// 绘制内圆:填充
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mColorErrorRing);
canvas.drawCircle(p.x, p.y, mInnerRingRadius, mPaint);
}
/**
* 4.3 绘制解锁图案连接的方向
*
* @param canvas
*/
private void drawDirectionArrow(Canvas canvas) {
// 绘制方向图标
if (sPoints.size() <= 0) {
return;
}
Point tp = sPoints.get(0);
for (int i = 1; i < sPoints.size(); i++) {
//根据移动的方向绘制方向图标
Point p = sPoints.get(i);
if (p.state == Point.STATE_CHECK_ERROR) {
drawDirectionArrow(canvas, tp, p, mColorErrorRing);
} else {
drawDirectionArrow(canvas, tp, p, mColorOnRing);
}
tp = p;
}
}
//绘制方向图标,三角形指示标
private void drawDirectionArrow(Canvas canvas, Point a, Point b, int color) {
//获取角度
float degrees = LockUtil.getDegrees(a, b) + 90;
//根据两点方向旋转
canvas.rotate(degrees, a.x, a.y);
drawArrow(canvas, a, color);
//旋转方向
canvas.rotate(-degrees, a.x, a.y);
}
//绘制三角形指示标
private void drawArrow(Canvas canvas, Point a, int color) {
// 绘制三角形,初始时是个默认箭头朝上的一个等腰三角形,用户绘制结束后,根据由两个GestureLockView决定需要旋转多少度
Path mArrowPath = new Path();
float offset = mInnerBackgroudRadius + (mArrowLength + mRadius - mInnerBackgroudRadius) / 2;//偏移量,定位三角形位置
mArrowPath.moveTo(a.x, a.y - offset);
mArrowPath.lineTo(a.x - mArrowLength, a.y - offset
+ mArrowLength);
mArrowPath.lineTo(a.x + mArrowLength, a.y - offset
+ mArrowLength);
mArrowPath.close();
mArrowPath.setFillType(Path.FillType.WINDING);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(color);
canvas.drawPath(mArrowPath, mPaint);
}
//监听
private OnGestureListener onGestureListener;
//是否显示滑动方向 默认为显示
private boolean isShow = true;
//判断是否进入了手势验证过程:down点在某个宫格区域内,并触发多个move
private boolean checking = false;
private boolean isAutoClearAfterUp = true;
//用于执行重置九宫格状态
private Handler handler = new Handler();
//用于定时执行清除界面
private Runnable run = new Runnable() {
@Override
public void run() {
handler.removeCallbacks(run);
reset();
postInvalidate();
}
};
private final static long RESET_DELAY_MILLS = 500;
//选中圆的集合
private List<Point> sPoints = new ArrayList<Point>();
//判断是否正在绘制并且未到达下一个点:true时:用于绘制断头线
private boolean movingNoPoint = false;
//正在移动的x,y坐标:用于绘制连接到按下位置的线
float moveingX, moveingY;
/**
* 5 点击事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
handler.removeCallbacks(run);
movingNoPoint = false;
float ex = event.getX();
float ey = event.getY();
boolean isFinish = false;//用于标示一次 手势动作是否结束:down move move... up
Point p = null;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 点下
p = actionDown(ex, ey);
break;
case MotionEvent.ACTION_MOVE: // 移动
p = actionMove(ex, ey);
break;
case MotionEvent.ACTION_UP: // 提起
p = checkSelectPoint(ex, ey);
checking = false;
isFinish = true;
break;
default:
movingNoPoint = true;
break;
}
//手势未结束 且 在手势过程中 且 拿到了目标落点
if (!isFinish && checking && p != null) {
int rk = crossPoint(p);
if (rk == POINT_CROSS_WITHOUT_LAST) {
//与已记录的路径点的非最后一个点重叠
movingNoPoint = true;
moveingX = ex;
moveingY = ey;
} else if (rk == 0) {
//一个新点
p.state = Point.STATE_CHECK;
addPoint(p);
if(onGestureListener != null){
onGestureListener.onProgress(this, getIndex());
}
}
}
if (isFinish) {
actionFinish();
}
postInvalidate();
return true;
}
//5.1 按下
//1. 取消清除密码的指令
//2. 重置所有的点状态
//3. 判断是否落入某个宫格内点,并返回该点
private Point actionDown(float ex, float ey) {
reset(); // 重置所有的点状态
Point p = checkSelectPoint(ex, ey);//判断落点位置
if (p != null) {
//如果在某个宫格内
checking = true;
}
return p;
}
//5.2 移动
//如果是在手势过程中:判断落点是否在某个宫格内点,并返回该点;不在的话,则记录断头位置
private Point actionMove(float ex, float ey) {
Point p = null;
if (checking) { //在手势验证过程中
p = checkSelectPoint(ex, ey);//检查落点位置是否在某个宫格区域内
if (p == null) {
movingNoPoint = true;
moveingX = ex;
moveingY = ey;
}
}
return p;
}
//5.3 解锁图案绘制完成
private void actionFinish() {
//5.3.1 抬起500hs后,重置整个九宫状态
if(isAutoClearAfterUp){
handler.postDelayed(run, RESET_DELAY_MILLS);
}
//5.3.2 只是一个点的话,直接取消选中,重置状态
if (this.sPoints.size() == 1) {
this.reset();
return;
}
if(onGestureListener != null){
onGestureListener.onComplete(this, getIndex());
}
}
/******************************Utils****************************************/
//重置点状态:清除选中点 + 所有点变为初始状态
public void reset() {
for (Point p : sPoints) {
p.state = Point.STATE_NORMAL;
}
sPoints.clear();
}
//检查落点位置是否在某个宫格区域内
private Point checkSelectPoint(float x, float y) {
for (int i = 0; i < mPoints.length; i++) {
for (int j = 0; j < mPoints[i].length; j++) {
Point p = mPoints[i][j];
if (LockUtil.checkInRound(p.x, p.y, mRadius, (int) x, (int) y)) {
return p;
}
}
}
return null;
}
//判断目标点是否与已记录路径点有交叉
private final static int POINT_NO_CROSS = 0;//新点
private final static int POINT_CROSS_WITH_LAST = 1;//与上一点重叠
private final static int POINT_CROSS_WITHOUT_LAST = 2;//与非最后一点重叠
private int crossPoint(Point targetP) {
// 重叠的不最后一个则 reset
if (sPoints.contains(targetP)) {
if (sPoints.size() > 2) {
// 与非最后一点重叠
if (sPoints.get(sPoints.size() - 1).index != targetP.index) {
return POINT_CROSS_WITHOUT_LAST;
}
}
return POINT_CROSS_WITH_LAST; // 与最后一点重叠
} else {
return POINT_NO_CROSS; // 新点
}
}
//向选中点集合中添加一个点
private void addPoint(Point point) {
this.sPoints.add(point);
}
//设置全部已经选中的为错误
private void errorPoints() {
for (Point p : sPoints) {
p.state = Point.STATE_CHECK_ERROR;
}
}
private int[] getIndex(){
//得到有序索引传
int[] indexs = new int[sPoints.size()];
for (int i = 0; i < sPoints.size(); i++) {
indexs[i] = sPoints.get(i).index;
}
return indexs;
}
/******************************API***************************************/
@Override
public void clear(){
reset();
postInvalidate();
if(onGestureListener != null){
onGestureListener.onClear(this);
}
}
@Override
public void error(){
errorPoints();
postInvalidate();
}
/**
* 设置监听
*
* @param onGestureListener
*/
public void setOnGestureListener(OnGestureListener onGestureListener) {
this.onGestureListener = onGestureListener;
}
/**
* 轨迹球画完监听事件
*/
public interface OnGestureListener {
void onProgress(GestureLockViewController controller, int[] indexs);
void onComplete(GestureLockViewController controller, int[] indexs);
void onClear(GestureLockViewController controller);
}
/******************************其他***************************************/
public void setShow(boolean show) {
isShow = show;
}
}
(4.2.43)手势解锁之九宫格控件GestureLockView
最新推荐文章于 2024-05-25 09:34:45 发布