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下载

原创粉丝点击