自定义View学习——九宫格解锁(LockPatternView)

虽然现在的应用很少在使用九宫格解锁,不过系统的应用锁还是可以见到的。实现的效果如下:
自定义View学习——九宫格解锁(LockPatternView)_第1张图片
分析

  • 首先需要绘制出九宫格,每一个单元格有两个半径不同的同心圆。
  • 当我们的手指在九宫格上触摸时,如果我们触摸在某个单元格的圆内就算选中了该单元,然后将其加入集合中。
  • 在触摸的过程中如果某个单元已经被触摸过,即已经被包含在集合中,就不重复将其加入集合。
  • 在触摸的过程中触摸了某个圆,就将其状态设为被触摸。
  • 手指抬起后如果触摸的圆小于4就将所有被触摸过加入集合的圆状态设为错误。
  • 在手指触摸的过程中不断调用invalidate(),在onDraw方法中根据圆的状态设置画笔的颜色进行绘制。遍历集合中被选中的圆,绘制两圆之间的线。
  • 在手指滑动的过程中,绘制最后一个被加入集合的圆和当前手指触摸点的坐标之间的连线。
  • 绘制两点(x1,y1)、(x2,y2)之间的直线,这两点是两圆心之间的连线与内圆的交点,求出dx,dy后就可以确定(x1,y1)、(x2,y2)

自定义View学习——九宫格解锁(LockPatternView)_第2张图片

自定义属性


        
        
        
        
        
    

实现代码

/**
 * 创建者: mao
 * 功能描述:九宫格解锁View
 */

public class LockPatternView extends View{

    private int mNormalColor;
    private int mTouchedColor;
    private int mErrorColor;

    private float mLineStrokeWidth;
    private float mCircleStrokeWidth;

    private Paint mOuterPaint;
    private Paint mInnerPaint;
    private Paint mLinePaint;

    private float mMotionX;
    private float mMotionY;

    //按下的时候如果在某个圆内
    private boolean isTouched;

    //所有被选中的圆的集合
    private List mTouchedCells=new ArrayList<>();

    private OnPatternListener mOnPatternListener;

    public LockPatternView(Context context) {
        this(context,null);
    }

    public LockPatternView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LockPatternView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(attrs);
        initPaint();//初始化画笔
    }

    public void setOnPatternListener(OnPatternListener onPatternListener) {
        mOnPatternListener = onPatternListener;
    }

    private void initAttrs( @Nullable AttributeSet attrs) {
        TypedArray typedArray=getContext().obtainStyledAttributes(attrs, R.styleable.LockPatternView);
        mNormalColor=typedArray.getColor(R.styleable.LockPatternView_normalColor,Color.parseColor("#FFC0CB"));
        mTouchedColor=typedArray.getColor(R.styleable.LockPatternView_touchedColor,Color.parseColor("#00BFFF"));
        mErrorColor=typedArray.getColor(R.styleable.LockPatternView_errorColor,Color.parseColor("#DC143C"));
        mLineStrokeWidth=typedArray.getDimension(R.styleable.LockPatternView_lineStrokeWidth,DensityUtil.dp2px(getContext(),5));
        mCircleStrokeWidth=typedArray.getDimension(R.styleable.LockPatternView_circleStrokeWidth,DensityUtil.dp2px(getContext(),5));
        typedArray.recycle();
    }

    private void initPaint() {
        //外圆画笔
        mOuterPaint=new Paint();
        mOuterPaint.setAntiAlias(true);
        mOuterPaint.setColor(mNormalColor);
        mOuterPaint.setStyle(Paint.Style.STROKE);
        mOuterPaint.setStrokeWidth(mCircleStrokeWidth);

        //内圆画笔
        mInnerPaint=new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setColor(mNormalColor);
        mInnerPaint.setStyle(Paint.Style.STROKE);
        mOuterPaint.setStrokeWidth(mCircleStrokeWidth);

        //线条的画笔
        mLinePaint=new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(mNormalColor);
        mLinePaint.setStrokeWidth(mLineStrokeWidth);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int minimumWidth = getSuggestedMinimumWidth();
        final int minimumHeight = getSuggestedMinimumHeight();
        int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
        int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
        setMeasuredDimension(viewWidth, viewHeight);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();

        //x,y方向到九宫格的偏移
        int offsetX;
        int offsetY;

        //单元格的宽度
        int cellWidth;

        if (viewHeight>viewWidth){
            offsetX =0;
            offsetY =(viewHeight - viewWidth)/2;
            cellWidth = viewWidth /3;
        }else {
            offsetX =(viewWidth - viewHeight)/2;
            offsetY =0;
            cellWidth = viewHeight /3;
        }

        Cell.initCircleRadius(cellWidth /6, cellWidth /14);
        Cell.createCells(offsetX, offsetY, cellWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Cell[][] cells=Cell.getsCells();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                Cell cell=cells[i][j];
                //根据圆的状态设置画笔颜色
                if (cell.isNormal()){
                    mOuterPaint.setColor(mNormalColor);
                    mInnerPaint.setColor(mNormalColor);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.outerCircleRadius,mOuterPaint);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.innerCircleRadius,mInnerPaint);
                }
                if (cell.isTouched()){
                    mOuterPaint.setColor(mTouchedColor);
                    mInnerPaint.setColor(mTouchedColor);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.outerCircleRadius,mOuterPaint);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.innerCircleRadius,mInnerPaint);
                }
                if (cell.isError()){
                    mOuterPaint.setColor(mErrorColor);
                    mInnerPaint.setColor(mErrorColor);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.outerCircleRadius,mOuterPaint);
                    canvas.drawCircle(cell.centerX,cell.centerY,Cell.innerCircleRadius,mInnerPaint);
                }
            }
        }
        //绘制线
        drawLine(canvas);
    }


    private Cell tempCell=new Cell(-1);

    //绘制线条
    private void drawLine(Canvas canvas) {
        if (mTouchedCells.size()>0){
            Cell lastCell=mTouchedCells.get(0);
            //遍历绘制出所有被触摸过的圆之间的连线
            for (int i=1;i0){
            for (Cell touchedCell : mTouchedCells) {
                touchedCell.setStatus(Cell.CellState.STATUS_ERROR);
            }
            mLinePaint.setColor(mErrorColor);

            notifyPatternError();
        }
    }

    private void notifyPatternStarted() {
        if (mOnPatternListener!=null){
            mOnPatternListener.onPatternStart();
        }
    }

    private void notifyPatternEnd() {
        if (mOnPatternListener != null) {
            StringBuilder builder=new StringBuilder();
            for (Cell cell : mTouchedCells) {
                builder.append(cell.index);
            }
            mOnPatternListener.onPatternEnd(builder.toString());
        }
    }

    private void notifyPatternError() {
        if (mOnPatternListener!=null){
            mOnPatternListener.onPatternError(mTouchedCells);
        }
    }

    //重置状态
    public void resetPattern() {
        Cell[][] cells =Cell.getsCells();

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                Cell cell=cells[i][j];
                cell.setStatus(Cell.CellState.STATUS_NORMAL);
            }
        }
        mLinePaint.setColor(mNormalColor);
        mTouchedCells.clear();
    }

    private int resolveMeasured(int measureSpec, int desired)
    {
        int result;
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.UNSPECIFIED:
                result = desired;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.max(specSize, desired);
                break;
            case MeasureSpec.EXACTLY:
            default:
                result = specSize;
        }
        return result;
    }


    //九宫格——单元
    public static final class Cell {
         float centerX;
         float centerY;
        final int index;

        private static Cell[][] sCells ;

        //外圆的半径
        static int outerCircleRadius;
        //内圆的半径
        static int innerCircleRadius;

        int status;

        //创建九宫格Cell[3][3]
        static void createCells(int offsetX, int offsetY, int cellWidth) {
            if (sCells==null){
                Cell[][] cells = new Cell[3][3];
                int index=0;
                for (int i = 0; i < 3; i++) {
                    for (int j = 0; j < 3; j++) {
                        cells[i][j] = new Cell(offsetX+cellWidth*(j*2+1)/2, offsetY+(i*2+1)*cellWidth/2, index++);
                    }
                }
                sCells=cells;
            }
        }

        void setStatus(int status){
            this.status=status;
        }

        public boolean isNormal(){
            return status == CellState.STATUS_NORMAL;
        }

        boolean isTouched(){
            return status == CellState.STATUS_TOUCHED;
        }

        boolean isError(){
            return status == CellState.STATUS_ERROR;
        }


        static Cell[][] getsCells(){
            return sCells;
        }

        private Cell(int index) {
            this.index=index;
        }

        private Cell(float centerX, float centerY, int index) {
            this.centerX = centerX;
            this.centerY = centerY;
            this.index=index;
        }

        //初始化圆的半径,外圆半径和内圆半径
        static void initCircleRadius(int outerCircleRadius, int innerCircleRadius) {
            Cell.outerCircleRadius=outerCircleRadius;
            Cell.innerCircleRadius=innerCircleRadius;
        }

        //如果触摸位置 x,y在某个单元格的圆内就返回该单元
        private static Cell checkRange(float x, float y) {
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    Cell cell=sCells[i][j];
                    if (!(((cell.centerX-x)*(cell.centerX-x)+(cell.centerY-y)*(cell.centerY-y))>outerCircleRadius*outerCircleRadius)){
                        return cell;
                    }
                }
            }
            return null;
        }

        //判断当前的触摸位置是否在某个单元中
        static boolean isInCircle(float x, float y) {
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    Cell cell=sCells[i][j];
                    if (!(((cell.centerX-x)*(cell.centerX-x)+(cell.centerY-y)*(cell.centerY-y))>outerCircleRadius*outerCircleRadius)){
                        return true;
                    }
                }
            }
            return false;
        }


        void setCenter(float motionX, float motionY) {
            this.centerX = motionX;
            this.centerY = motionY;
        }

        //单元格的状态
        static class CellState {
            static int STATUS_NORMAL=0;
            static int STATUS_TOUCHED=1;
            static int STATUS_ERROR=2;
        }
    }

    public static class MathUtil{
        /**
         * 计算两点之间的距离(x1,y1)--(x2,y2)
         */
        static float distance(float x1, float y1, float x2, float y2){
            float d2=(x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
            return (float) Math.sqrt(d2);
        }
    }

    public static class DensityUtil {

        private DensityUtil() {
            throw new UnsupportedOperationException("cannot be instantiated");
        }

        /**
         * dp转px
         */
        static int dp2px(Context context, float dpVal) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dpVal, context.getResources().getDisplayMetrics());
        }
    }

    /**
     * 回调接口
     */
    public interface OnPatternListener {

        void onPatternStart();

        void onPatternEnd(String pattern);

        void onPatternError(List cells);
    }

}

参考:Touch事件分发 - 九宫格解锁

你可能感兴趣的:(自定义View学习——九宫格解锁(LockPatternView))