自己的第一篇博客,以前从来没有过记录博客的习惯,后来发现很多知识一段时间不用后来很容易就忘记了!而现在这段时间正好在自己学习自定义view开发,所以准备开始培养自己写博客记录的习惯!
这篇博客主要是参考了鸿洋大神的文章写的,基本上算是把大神的代码重新敲了一遍,但是其中也有些自己的改动!
Android 手势锁的实现 让自己的应用更加安全吧
首先是GestureLockView类,这个类就代表了每一个小圆圈,小圆圈的状态被分成了三个:默认状态,被选中,被选中后手指放开;然后就是根据圆圈的不同状态进行圆圈的绘制,因为要在GestureViewGroup中控制小圆圈的显示状态所以就需要在GestureLockView中提供修改圆圈状态的方法
public void setMode(Mode mode) {
this.mCurrentMode = mode;
invalidate();
}
另外小三角的角度也是在用户手指滑动过程中来确定的,所以也需要提供一个改变三角角度的方法
public void setArrowDegree(int degree) {
this.mArrowDegree = degree;
}
到现在,基本上GestureLockVIew就结束了,其他的就是去draw,比较简单!现在贴出来整个代码,可以看看
package com.example.gesturelock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.view.View;
public class GestureLockView extends View {
/**
* STATUS_NO_FINGER 默认的状态 STATUS_FINGER_ON 当被选中时 STATUS_FINGER_UP 当选中后放手
*
* @author
*/
enum Mode {
STATUS_NO_FINGER, STATUS_FINGER_ON, STATUS_FINGER_UP
}
private int colorNoFingetInner;
private int colorNofingerOutter;
private int colorFingerOn;
private int colorFingerUp;
private Mode mCurrentMode = Mode.STATUS_NO_FINGER;
private Paint mPaint;
private Path mArrowPath;
private int mWidth;// view的宽
private int mHeight;// view的高
private int mRadius;// 半径
private int mCenterX;// 圆心x坐标
private int mCenterY;// 圆心Y坐标
private int mStrokeWidth = 2;
private float mArrowLength;// 三角形的高度
private int mArrowDegree = -1;
public GestureLockView(Context context, int colorNoFingetInner, int colorNofingerOutter, int colorFingerOn, int colorFingerUp, int mWidth) {
super(context);
/**
* 进行初始化的操作:
* 1.确定几种颜色
* 2.初始化画笔mPaint
* 3.初始化要画的三角的路径
* ---此处和鸿洋大神有点不一样,我在此处的圆圈的宽度是在父ViewGroup算出后传进来的
*/
this.colorNoFingetInner = colorNoFingetInner;
this.colorNofingerOutter = colorNofingerOutter;
this.colorFingerOn = colorFingerOn;
this.colorFingerUp = colorFingerUp;
this.mWidth = this.mHeight = mWidth;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mArrowPath = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
mRadius = mCenterX = mCenterY = mWidth / 2;
mArrowLength = (float) (mRadius * 0.3);
mRadius -= mStrokeWidth / 2;
mArrowPath.moveTo(mCenterX, mStrokeWidth + 2);
mArrowPath.lineTo(mCenterX - mArrowLength * 0.8F, mStrokeWidth + 2 + mArrowLength);
mArrowPath.lineTo(mCenterX + mArrowLength * 0.8F, mStrokeWidth + 2 + mArrowLength);
mArrowPath.close();
mArrowPath.setFillType(FillType.WINDING);
}
@Override
protected void onDraw(Canvas canvas) {
switch (mCurrentMode) {
case STATUS_NO_FINGER:
// 默认状态下
mPaint.setStyle(Style.FILL);
mPaint.setColor(colorNofingerOutter);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
mPaint.setColor(colorNoFingetInner);
canvas.drawCircle(mCenterX, mCenterY, mRadius * 0.2f, mPaint);
break;
case STATUS_FINGER_ON:
// 被选中状态
mPaint.setStyle(Style.STROKE);
mPaint.setColor(colorFingerOn);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
mPaint.setStyle(Style.FILL);
canvas.drawCircle(mCenterX, mCenterY, mRadius * 0.2f, mPaint);
break;
case STATUS_FINGER_UP:
// 被选中后放开
mPaint.setColor(colorFingerUp);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
mPaint.setStyle(Style.FILL);
canvas.drawCircle(mCenterX, mCenterY, mRadius * 0.2f, mPaint);
// 画小三角形
drawArray(canvas);
break;
default:
break;
}
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(20);
String id = String.valueOf(getId());
canvas.drawText(id, 0, id.length(), mCenterX, mCenterY, mPaint);
}
private void drawArray(Canvas canvas) {
if (mArrowDegree != -1) {
mPaint.setStyle(Style.FILL);
canvas.save();
canvas.rotate(mArrowDegree, mCenterX, mCenterY); // 旋转的不是画布,而是画布的坐标系
canvas.drawPath(mArrowPath, mPaint);
canvas.restore();
}
}
/**
* 设置当前模式并重绘界面
*
* @param mode
*/
public void setMode(Mode mode) {
this.mCurrentMode = mode;
invalidate();
}
/**
* 设置小三角的角度
* @param degree
*/
public void setArrowDegree(int degree) {
this.mArrowDegree = degree;
}
public int getArrowDegree() {
return this.mArrowDegree;
}
}
下面就是相对有点麻烦的GestureLockViewGroup了!基本上都写到注释里面了!
package com.example.gesturelock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.example.gesturelock.GestureLockView.Mode;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
public class GestureLockViewGroup extends RelativeLayout {
public static String Tag = "GestureLockViewGroup";
private int mNoFingerInnerColor = Color.parseColor("#4B4B4B");
private int mNoFingerOutterColor = Color.parseColor("#111111");
private int mFingerOnColor = Color.parseColor("#0000FF");
private int mFingerUpColor = Color.parseColor("#EC1328");
private int mLineViewCount = 3;
private int mTotalTryTimes = 5;
private int mTryTimes = 5;
private Paint mPaint;
private Path mPath;
private int mWidth;
private int mHeight;
private GestureLockView[] mGestureLockViews;
private int mGestureLockViewWidth;
private int mMarginBetweenLockView;
private List<Integer> mChoice;
private int mLastPathX;
private int mLastPathY;
private Point mTemTerminal;
private GestureLockViewGroupListener gestureLockViewGroupListener;
private Integer[] mAnswer = { 1, 2, 5, 8 };//这里答案写在类里了,实际使用中可以把密码存在本地sp中
public GestureLockViewGroup(Context context) {
this(context, null);
}
public GestureLockViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureLockViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/**
* 获取自定义属性的值
*/
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GestureLockViewGroup, defStyle, 0);
int indexCount = array.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int index = array.getIndex(i);
switch (index) {
case R.styleable.GestureLockViewGroup_no_finger_inner_color:
mNoFingerInnerColor = array.getColor(index, Color.parseColor("#4B4B4B"));
break;
case R.styleable.GestureLockViewGroup_no_finger_outter_color:
mNoFingerOutterColor = array.getColor(index, Color.parseColor("#111111"));
break;
case R.styleable.GestureLockViewGroup_finger_on_color:
mFingerOnColor = array.getColor(index, Color.parseColor("#0000FF"));
break;
case R.styleable.GestureLockViewGroup_finger_up_color:
mFingerUpColor = array.getColor(index, Color.parseColor("#EC1328"));
break;
case R.styleable.GestureLockViewGroup_line_view_count:
mLineViewCount = array.getInt(index, 3);
break;
case R.styleable.GestureLockViewGroup_try_times:
mTryTimes = array.getInt(index, 5);
default:
break;
}
}
array.recycle();
/**
* 初始化操作:
* 1.初始化画笔
* 2.初始化用户手指划过的圆圈的路径
* 3.其他对象的实例化
*/
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPath = new Path();
mChoice = new ArrayList<>();
mTemTerminal = new Point();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// mWidth = MeasureSpec.getSize(widthMeasureSpec);
// mHeight = MeasureSpec.getSize(heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
/**
* 这个地方跟大神有点不一样,这个地方我自己也还有点疑问!
* 被注释的是大神的,但是如果我在布局中用确定的值,并且宽大于高时会有点问题,然后改成了下面自己的
*/
mHeight = mWidth = mWidth > mHeight ? mHeight : mWidth;//取宽高的最小值
if (mGestureLockViews == null) {
mGestureLockViews = new GestureLockView[mLineViewCount * mLineViewCount];
mGestureLockViewWidth = (int) (4 * mWidth * 1.0f / (5 * mLineViewCount + 1));//计算出每个小圆圈的宽度,在new GestureLockView的时候作为参数传入
mMarginBetweenLockView = (int) (mGestureLockViewWidth * 0.25);//设定圆圈之间的间距
mPaint.setStrokeWidth(mGestureLockViewWidth * 0.25f);//设定描绘用户手指路径的画笔的宽度
for (int i = 0; i < mGestureLockViews.length; i++) {
mGestureLockViews[i] = new GestureLockView(getContext(), mNoFingerInnerColor, mNoFingerOutterColor, mFingerOnColor, mFingerUpColor, mGestureLockViewWidth);
mGestureLockViews[i].setId(i + 1);//代码给每个GestureLockView添加id 用于记录用户手指划过的view 对比是否是正确密码也是靠这个
RelativeLayout.LayoutParams lockParams = new RelativeLayout.LayoutParams(mGestureLockViewWidth, mGestureLockViewWidth);
if (i % mLineViewCount != 0) {
lockParams.addRule(RelativeLayout.RIGHT_OF, mGestureLockViews[i - 1].getId());
}
if (i > mLineViewCount - 1) {
lockParams.addRule(RelativeLayout.BELOW, mGestureLockViews[i - mLineViewCount].getId());
}
int rightMargin = mMarginBetweenLockView;
int bottomMargin = mMarginBetweenLockView;
int leftMargin = 0;
int topMargin = 0;
if (i >= 0 && i < mLineViewCount) {
topMargin = mMarginBetweenLockView;
}
if (i % mLineViewCount == 0) {
leftMargin = mMarginBetweenLockView;
}
lockParams.setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
addView(mGestureLockViews[i], lockParams);
Log.i(Tag, i + ":left +" + leftMargin + ":topMargin +" + topMargin + ":rightMargin +" + rightMargin + ":rightMargin +" + leftMargin);
}
}
}
/**
* 手指的处理逻辑:
* 1.down的时候将相关数据恢复初始化
* 2.move的时候:
* 当手指移动到某一点上的时候,判断改点是否已经被选择过了,没有被选过就加入到mChoice中,并更改状态为finget_on
* 当一个点的id被加入后,更新最后一个点的坐标为最后选择的那个点的中心坐标并且确定一个此时临时的终点就是手指的位置
* 3.up的时候:
* 更改所有已经被选中的状态未finger_up
* 判断是否次数已经超标,超标就直接结束
* 然后去校验手指路径上的id是否跟密码一致
* 去根据相邻两个圆圈的圆心坐标去确定两者中前者的小三角的角度并设置给小圆圈
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
reset();
break;
case MotionEvent.ACTION_MOVE:
mPaint.setColor(mFingerOnColor);
mPaint.setAlpha(50);
GestureLockView gestureLockView = getChildByPos(x, y);
if (gestureLockView != null) {
int id = gestureLockView.getId();
if (!mChoice.contains(id)) {
if (gestureLockViewGroupListener != null) {
gestureLockViewGroupListener.onBlockSelected(id);
}
mChoice.add(gestureLockView.getId());
gestureLockView.setMode(Mode.STATUS_FINGER_ON);
mLastPathX = gestureLockView.getLeft() / 2 + gestureLockView.getRight() / 2;
mLastPathY = gestureLockView.getTop() / 2 + gestureLockView.getBottom() / 2;
if (mChoice.size() == 1) {
mPath.moveTo(mLastPathX, mLastPathY);
} else {
mPath.lineTo(mLastPathX, mLastPathY);
}
}
}
mTemTerminal.x = x;
mTemTerminal.y = y;
break;
case MotionEvent.ACTION_UP:
mPaint.setColor(mFingerUpColor);
mPaint.setAlpha(50);
mTemTerminal.x = mLastPathX;
mTemTerminal.y = mLastPathY;
changeItemModeToUp();
for (int i = 0; i < mChoice.size() - 1; i++) {
Integer firstChildId = mChoice.get(i);
Integer nextChildId = mChoice.get(i + 1);
GestureLockView firstChild = (GestureLockView) findViewById(firstChildId);
GestureLockView nextChild = (GestureLockView) findViewById(nextChildId);
int dx = nextChild.getLeft() - firstChild.getLeft();
int dy = nextChild.getTop() - firstChild.getTop();
int angle = (int) Math.toDegrees(Math.atan2(dy, dx)) + 90;
firstChild.setArrowDegree(angle);
}
this.mTryTimes--;
if (mTryTimes < 0) {
if (gestureLockViewGroupListener != null) {
gestureLockViewGroupListener.isOutOfTryTime(true);
}
break;
} else {
if (gestureLockViewGroupListener != null) {
gestureLockViewGroupListener.isOutOfTryTime(false);
}
}
Integer[] array = mChoice.toArray(new Integer[] {});
if (gestureLockViewGroupListener != null) {
gestureLockViewGroupListener.isGestureMatched(checkAnswer(array));
}
if (checkAnswer(array)) {
mTryTimes = mTotalTryTimes;
}
break;
default:
break;
}
invalidate();
return true;
}
/**
* 用于判断手势密码是否正确
* @param array
* @return
*/
private boolean checkAnswer(Integer[] array) {
if (array.length != mAnswer.length) {
return false;
}
for (int i = 0; i < array.length; i++) {
if (array[i] != mAnswer[i]) {
return false;
}
}
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//这时画的是已经选择的点之间的连线
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
//这时画的是最后一个选择点和手指未释放时之间的线,是实时改变的
if (mChoice.size() > 0) {
canvas.drawLine(mLastPathX, mLastPathY, mTemTerminal.x, mTemTerminal.y, mPaint);
}
}
private void changeItemModeToUp() {
for (GestureLockView gestureLockView : mGestureLockViews) {
if (mChoice.contains(gestureLockView.getId())) {
gestureLockView.setMode(Mode.STATUS_FINGER_UP);
}
}
}
private void reset() {
mChoice.clear();
mPath.reset();
for (int i = 0; i < mGestureLockViews.length; i++) {
mGestureLockViews[i].setMode(Mode.STATUS_NO_FINGER);
mGestureLockViews[i].setArrowDegree(-1);
}
}
private GestureLockView getChildByPos(int x, int y) {
for (GestureLockView gestureLockView : mGestureLockViews) {
if (checkPositionInChild(gestureLockView, x, y)) {
return gestureLockView;
}
}
return null;
}
private boolean checkPositionInChild(View child, int x, int y) {
int padding = (int) (mGestureLockViewWidth * 0.15);
if (x >= child.getLeft() + padding && x <= child.getRight() - padding && y >= child.getTop() + padding && y <= child.getBottom() - padding) {
return true;
}
return false;
}
public void setGestureViewGroupListener(GestureLockViewGroupListener listener) {
this.gestureLockViewGroupListener = listener;
}
public interface GestureLockViewGroupListener {
public void onBlockSelected(int cid);
public void isGestureMatched(boolean matched);
public void isOutOfTryTime(boolean outOfTryTime);
}
public void setAnswer(Integer[] setedAnswer) {
this.mAnswer = setedAnswer;
}
}
之前看大神代码的时候,对确定gestureLockView的宽度的算法还有小三角的角度比较难懂!后来自己在纸上画画就了解了!
宽度问题设置个XY什么的算算很简单!初中级别的!
角度问题也就是个简单的反三角函数问题!不赘述!
现在如果你设置view为fill_parent的话,默认是靠顶部的!设置一下gravity就好了!