Android手势密码原理分析
来源:互联网 发布:淘宝兼职被骗天涯论坛 编辑:程序博客网 时间:2024/05/22 17:03
在上一篇介绍了手势密码的使用,这一篇将主要介绍手势密码的原理,手势密码的功能主要是由自定义PatternLockView实现的。那咱这就一步一步来揭开PatternLockView的面纱。
效果图
步骤
第一步
自定义PatternLockView继承View,重写两个构造方法,一个在xml中定义会调用,一个在java代码中创建对象会调用。但不管怎么定义,都会走到这个构造中。
public PatternLockView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PatternLockView); try { sDotCount = typedArray.getInt(R.styleable.PatternLockView_dotCount, DEFAULT_PATTERN_DOT_COUNT); mPathWidth = (int) typedArray.getDimension(R.styleable.PatternLockView_pathWidth, ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_path_width)); mNormalDotStateColor = typedArray.getColor(R.styleable.PatternLockView_normalDotStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mCorrectDotStateColor = typedArray.getColor(R.styleable.PatternLockView_correctDotStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mCorrectDotStrokeColor = typedArray.getColor(R.styleable.PatternLockView_correctDotStrokeStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mCorrectLineStateColor = typedArray.getColor(R.styleable.PatternLockView_correctLineStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mWrongLineStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongLineStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mWrongDotStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongDotStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mWrongDotStrokeStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongDotStrokeStateColor, ResourceUtils.getColor(getContext(), R.color.white)); mDotNormalSize = (int) typedArray.getDimension(R.styleable.PatternLockView_dotNormalSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_size)); mDotSelectedSize = (int) typedArray.getDimension(R.styleable.PatternLockView_dotSelectedSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_selected_size)); mDotAnimationDuration = typedArray.getInt(R.styleable.PatternLockView_dotAnimationDuration, DEFAULT_DOT_ANIMATION_DURATION); } finally { typedArray.recycle(); } //获取绘制点的个数 mPatternSize = sDotCount * sDotCount; //存放选中的点 mPattern = new ArrayList<>(mPatternSize); //二维数组 选中点时置为true mPatternDrawLookup = new boolean[sDotCount][sDotCount]; //二维数组 存放点对象 mDotStates = new DotState[sDotCount][sDotCount]; //通过循环的方式,创建点对象,并对点初始化大小 for (int i = 0; i < sDotCount; i++) { for (int j = 0; j < sDotCount; j++) { mDotStates[i][j] = new DotState(); mDotStates[i][j].mSize = mDotNormalSize; } } //存放监听对象 mPatternListeners = new ArrayList<>(); initView(); }
这里主要拿到我们自定义的点的数量 点的颜色 线的颜色 线的宽度等信息,创建了存放选中点的集合 存放监听对象的集合和两个二维数组及点对象。
再来看下initView()方法
private void initView() { //设置View可点击 setClickable(true); //设置点与点连线画笔 mPathPaint = new Paint(); mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); mPathPaint.setColor(mCorrectLineStateColor); mPathPaint.setStyle(Paint.Style.STROKE); //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式Cap.ROUND,或方形样式Cap.SQUARE mPathPaint.setStrokeJoin(Paint.Join.ROUND); //设置绘制时各图形的结合方式,如平滑效果等 mPathPaint.setStrokeCap(Paint.Cap.ROUND); mPathPaint.setStrokeWidth(mPathWidth); //设置画点的画笔 mDotPaint = new Paint(); mDotPaint.setAntiAlias(true); mDotPaint.setDither(true); //设置点外环的画笔 mRingPaint = new Paint(); mRingPaint.setAntiAlias(true); mRingPaint.setDither(true); //设置点放大缩小动画(用于隐藏模式) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !isInEditMode()) { mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.fast_out_slow_in); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.linear_out_slow_in); } }
这里主要对各个画笔做了初始化工作,也可以通过Java代码进行更改,初始化工作完成后,就要捕捉手势了。
第二步
通过onTouchEvent()方法获取手势坐标
@Overridepublic boolean onTouchEvent(MotionEvent event) { //判断View是否可用 if (!mInputEnabled || !isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: handleActionDown(event); return true; case MotionEvent.ACTION_UP: handleActionUp(event); return true; case MotionEvent.ACTION_MOVE: handleActionMove(event); return true; case MotionEvent.ACTION_CANCEL: mPatternInProgress = false; resetPattern(); notifyPatternCleared(); return true; } return false; }
这里对手指的按下 移动 抬起 取消都做了判断
取消的操作比较简单,就是重置各个状态
下边分别来看下按下 移动 抬起
handleActionDown按下方法
private void handleActionDown(MotionEvent event) { //每次按下都重置下状态 resetPattern(); //获取手指按下的坐标,以View区域的左上顶点为(0,0)坐标 float x = event.getX(); float y = event.getY(); //通过detectAndAddHit方法判断首次按下是否触碰到点 Dot hitDot = detectAndAddHit(x, y); //如果触碰到点,则改变状态,开始画选中点颜色和与点的连线 //如果没有触碰上,则不画线(通过mPatternInProgress状态控制) if (hitDot != null) { mPatternInProgress = true; mPatternViewMode = CORRECT; notifyPatternStarted(); } else { mPatternInProgress = false; notifyPatternCleared(); } //如果首次触碰到点,则进行局部刷新 if (hitDot != null) { //获取选中点的中心坐标 float startX = getCenterXForColumn(hitDot.mColumn); float startY = getCenterYForRow(hitDot.mRow); float widthOffset = mViewWidth / 2f; float heightOffset = mViewHeight / 2f; // 局部刷新其实没多大用,在onDraw里边还是所有代码都走 invalidate((int) (startX - widthOffset), (int) (startY - heightOffset), (int) (startX + widthOffset), (int) (startY + heightOffset)); } //把首次按下的坐标赋值给mInProgressX mInProgressY //当选中时可开始画线 mInProgressX = x; mInProgressY = y; }
按下操作主要是获取首次按下坐标,并通过坐标拿到是否触碰到某一个点上,如果触碰到点上,则改变点的颜色并开始画点与手指的连线。
handleActionMove移动方法
private void handleActionMove(MotionEvent event) { float x = event.getX(); float y = event.getY(); Dot hitDot = detectAndAddHit(x, y); int patternSize = mPattern.size(); //手指按下时没有选中点,当滑动时,滑动到点上进入此判断语句 //改变mPatternInProgress mPatternViewMode状态,重画选中点和外环的颜色及开始画与点的连线 if (hitDot != null && patternSize == 1) { mPatternInProgress = true; mPatternViewMode = CORRECT; notifyPatternStarted(); } mInProgressX = event.getX(); mInProgressY = event.getY(); //简单粗暴 重新绘制 invalidate(); }
移动操作主要是根据不断移动的坐标,判断是否触碰到点上,如果触碰上了就添加到选中点的集合内,并重画选中点和外环的颜色及开始画与点的连线。
handleActionUp抬起方法
private void handleActionUp(MotionEvent event) { // 判断手势集合中是否有选中的点 if (!mPattern.isEmpty()) { //重置状态 阻止画最后一个点与手指之间的连线 mPatternInProgress = false; notifyPatternDetected(); //抬起的时候再绘制一次 invalidate(); } }
抬起操作主要是判断存放点的集合是否为空,如果不为空,则重置状态,阻止画最后一个点与手指之间的连线。
detectAndAddHit方法
/*** 根据x,y坐标确定是否点击到点上*/private Dot detectAndAddHit(float x, float y) { //通过checkForNewHit判断坐标是否在一个点上 final Dot dot = checkForNewHit(x, y); //如果在则返回点 如果不在则返回null if (dot != null) { Dot fillInGapDot = null; //拿到初始化定义存放选中点的集合 final ArrayList<Dot> pattern = mPattern; if (!pattern.isEmpty()) { //拿到集合中最后一个点 Dot lastDot = pattern.get(pattern.size() - 1); int dRow = dot.mRow - lastDot.mRow; int dColumn = dot.mColumn - lastDot.mColumn; int fillInRow = lastDot.mRow; int fillInColumn = lastDot.mColumn; //重新计算行 /** * 重新计算行 * 例如:从(0,1)直接到(2,1),想跳过(1,1)时,则通过此方法把第一行添加进去 */ if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) { fillInRow = lastDot.mRow + ((dRow > 0) ? 1 : -1); } //重新计算列 if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) { fillInColumn = lastDot.mColumn + ((dColumn > 0) ? 1 : -1); } // fillInGapDot = Dot.of(fillInRow, fillInColumn); } /** * 例如:从(0,1)直接到(2,1),想跳过(1,1)时,则通过此方法把第一行添加进去 */ if (fillInGapDot != null && !mPatternDrawLookup[fillInGapDot.mRow][fillInGapDot.mColumn]) { addCellToPattern(fillInGapDot); } /** * 如果中间跳过一个点则先添加跳过的点 * 如果没有则添加选中的点 */ addCellToPattern(dot); /** * 判断是否震动 */ if (mEnableHapticFeedback) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } return dot; } return null; }
detectAndAddHit方法主要是根据坐标拿到点,并判断前一个点与现在点中间是否还有必须经过但还有添加的点,如果有则优先添加中间点,如果没有则直接添加现在的点。
checkForNewHit方法
/** * 检查是否滑动到点上 */ private Dot checkForNewHit(float x, float y) { //判断y坐标是否在点上 final int rowHit = getRowHit(y); if (rowHit < 0) { return null; } //判断x坐标是否在点上 final int columnHit = getColumnHit(x); if (columnHit < 0) { return null; } /** * 如果已经选中,则不再选中 */ if (mPatternDrawLookup[rowHit][columnHit]) { return null; } //返回一个点对象 return Dot.of(rowHit, columnHit); }
checkForNewHit方法主要是根据x,y坐标判断是否在一个点上,如果是则返回一个点对象。
getRowHit getColumnHit方法
/** * 根据y坐标判断是否在某一个点的y坐标上 * if (y >= hitTop && y <= hitTop + hitSize)这行给点加了一个范围,在此范围内都算点上了点 */ private int getRowHit(float y) { final float squareHeight = mViewHeight; float hitSize = squareHeight * mHitFactor; float offset = getPaddingTop() + (squareHeight - hitSize) / 2f; for (int i = 0; i < sDotCount; i++) { float hitTop = offset + squareHeight * i; if (y >= hitTop && y <= hitTop + hitSize) { return i; } } return -1; } /** * 根据x坐标判断是否在某一个点的x坐标上 * if (x >= hitLeft && x <= hitLeft + hitSize)这行给点加了一个范围,在此范围内都算点上了点 */ private int getColumnHit(float x) { final float squareWidth = mViewWidth; float hitSize = squareWidth * mHitFactor; float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f; for (int i = 0; i < sDotCount; i++) { final float hitLeft = offset + squareWidth * i; if (x >= hitLeft && x <= hitLeft + hitSize) { return i; } } return -1; }
这两个方法是给点加了一个范围,在此范围内都算点上了点。
addCellToPattern方法
private void addCellToPattern(Dot newDot) { //将选中的点置为true mPatternDrawLookup[newDot.mRow][newDot.mColumn] = true; //给集合中添加选中的点 mPattern.add(newDot); //开始点放大缩小动画(隐藏模式用) startDotSelectedAnimation(newDot); //回调给用户选中的点 notifyPatternProgress(); }
addCellToPattern方法是将选中的点添到集合中,并把mPatternDrawLookup中点的位置改为true。
以上就是按下 移动 抬起的主要方法,下面再来看下点和线是如何绘制的。
第三步
首先通过onSizeChanged方法来确定patternlockview的大小
onSizeChanged方法
@Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { //设置每一个点占据的宽度大小 int adjustedWidth = width - getPaddingLeft() - getPaddingRight(); mViewWidth = adjustedWidth / (float) sDotCount; //设置每一个点占据的高度度大小 int adjustedHeight = height - getPaddingTop() - getPaddingBottom(); mViewHeight = adjustedHeight / (float) sDotCount; }
通过onSizeChanged方法,获取到一个点占据的宽和高
然后就是onDraw绘制方法了,所有的绘制工作都是由它来完成,同时在滑动过程中,此方法也是在不断的调用。
onDraw方法
@Override protected void onDraw(Canvas canvas) { //拿到选中点的集合 ArrayList<Dot> pattern = mPattern; int patternSize = pattern.size(); //拿到存放点是否选中的二维数组 boolean[][] drawLookupTable = mPatternDrawLookup; //拿到路径对象 Path currentPath = mCurrentPath; //清除所有的直线和曲线的路径,但保持内部数据结构,以便更快地重用。 currentPath.rewind(); //判断是否是隐藏模式 boolean drawPath = !mInStealthMode; if (drawPath) { //根据当前模式设置连接线的颜色 mPathPaint.setColor(getCurrentLineStateColor()); //是否绘制最后一个点与手指之间的连线 boolean anyCircles = false; float lastX = 0f; float lastY = 0f; for (int i = 0; i < patternSize; i++) { //拿到选中的点 Dot dot = pattern.get(i); //如果点没有被选中,则不需要跳出循环 if (!drawLookupTable[dot.mRow][dot.mColumn]) { return; } anyCircles = true; //拿到某点的中心坐标 float centerX = getCenterXForColumn(dot.mColumn); float centerY = getCenterYForRow(dot.mRow); if (i != 0) { DotState state = mDotStates[dot.mRow][dot.mColumn]; currentPath.rewind(); //当到第二个的时候,则lastX,lastY就是选中的第一个点的中心 currentPath.moveTo(lastX, lastY); if (state.mLineEndX != Float.MIN_VALUE && state.mLineEndY != Float.MIN_VALUE) {//主要用于自动绘制 currentPath.lineTo(state.mLineEndX, state.mLineEndY); } else {//手动绘制时进入的 currentPath.lineTo(centerX, centerY); } //把线连接上 canvas.drawPath(currentPath, mPathPaint); } //当是第一个时,先拿到第一个的中心点,然后从第一个开始画线,后续再把下一个点的中心点赋值 lastX = centerX; lastY = centerY; } //这里绘制最后一个点和和点连接的线 if (mPatternInProgress && anyCircles) { currentPath.rewind(); currentPath.moveTo(lastX, lastY); currentPath.lineTo(mInProgressX, mInProgressY); //把线连接上 canvas.drawPath(currentPath, mPathPaint); } } //循环画点 for (int i = 0; i < sDotCount; i++) { float centerY = getCenterYForRow(i);//排 for (int j = 0; j < sDotCount; j++) { DotState dotState = mDotStates[i][j]; float centerX = getCenterXForColumn(j); float size = dotState.mSize * dotState.mScale; float translationY = dotState.mTranslateY; drawCircle(canvas, (int) centerX, (int) centerY + translationY, size, drawLookupTable[i][j], dotState.mAlpha); } } }
drawCircle方法
private void drawCircle(Canvas canvas, float centerX, float centerY, float size, boolean partOfPattern, float alpha) { //如果隐藏模式则不走 if (partOfPattern && !isInStealthMode()) { //设置点外环的颜色 mRingPaint.setColor(getCurrentDotStrokeColor(partOfPattern)); //画点的外环 canvas.drawCircle(centerX, centerY, size, mRingPaint); } //画点 mDotPaint.setColor(getCurrentDotStateColor(partOfPattern)); mDotPaint.setAlpha((int) (alpha * 255)); canvas.drawCircle(centerX, centerY, size / 2, mDotPaint); }
在onDraw方法中对画什么颜色的点,什么颜色的线都做了判断。
至此整个绘制过程就算完成了,剩下的一些颜色的判断,各个状态的回调,各个模式都比较简单,就不再这里特别说明了。
贡献
这个库是从Aritra Roy的PatternLockView获取并添加了一些改进使其更加灵活,如果您发现bug或想改进它的任何方面,可以自由地用拉请求进行贡献。
源码下载
APK下载
- Android手势密码原理分析
- Android 手势密码分析
- Android手势密码LockPatternView、LockPasswordUtils、LockPatternUtils等分析
- android手势密码
- Android手势密码
- Android 手势密码
- android 手势密码
- Android手势密码
- Android手势密码
- Android手势密码解锁
- android---手势密码
- Android设置手势密码
- Android手势密码探索
- 《android手势密码》
- Android自定义手势密码
- Android手势密码
- Android手势密码实现方案
- Android手势密码实现方案
- java7.5
- PHP操作MongoDB时的整数问题及对策说明
- kylin从入门到实战:实际案例
- 手把手生教你成决策树
- Android studio 无法创建Android项目解决办法
- Android手势密码原理分析
- 二叉树-建树,层次遍历,先序遍历,中序遍历,后序遍历
- .NET-简单的饮品售货机
- 面试题转载---JVM加载class文件的原理机制
- 深度学习资料
- bzoj1655/洛谷1772/codevs1655 物流运输 某dfs+状压dp做法
- Webstorm快捷键大全
- vs 生成第三方库文件 以及 调用第三方库文件
- ORACLE 分割字段为多行数据