Android九宫格解锁自定义view

作者: 奔跑吧李博 | 来源:发表于2020-07-25 01:03 被阅读0次

    很早以前就想自己写一个九宫格解锁了,现在终于一鼓作气安排上了,难度在自定义view里面算是中等吧。主要是要有实现的思路,然后一步一步去实现你的想法,其中考验的就是你的算法能力。

    演示效果:

    Github代码地址

    实现思路

    1.创建一个Dot类存每个圆格的圆心坐标。
    2.在onmeasure()方法中设置圆的半径,和9个圆的中心点。
    3.在onTouchEvent()方法中,实时对当前触摸点和9个圆做碰撞检测,如果进入该圆,就将该圆序号存储起来。
    4.在onDraw()方法中,绘制每个外圆环和实心圆。遍历存储的点绘制路径,已经更改圆的颜色。

    核心点

    在onTouch事件中,需要实时对触碰点和9个圆做碰撞检测,判定方法为触碰点到圆心之前的距离小于半径,即为经过该圆,需要用到Api有次方公式Math.pow()和开方公司Math.sqrt()。

    主要代码实现(思路实现见注释)

    /**
     * create by libo
     * create on 2020/7/24
     * description 九宫格解锁自定义view
     */
    public class UnlockNineSquaresView extends View {
        /**
         * 记录9个点的坐标集合
         */
        private List<Dot> dots = new ArrayList<>();
        /**
         * 按照顺序记录需要连线的dot的序号,使用LinkedHashSet从而更满足有序不重复的特点
         */
        private LinkedHashSet<Integer> drawDots = new LinkedHashSet();
        private final int DOT_COUNT = 9;
        /* 自身宽度高度 */
        private int width;
        /**
         * 外圆环画笔
         */
        private Paint circlePaint;
        /**
         * 内实心圆画笔
         */
        private Paint innerDotPaint;
        /**
         * 内实心半透明画笔
         */
        private Paint transparentPaint;
        /** 连线画笔 */
        private Paint linePaint;
        /** 外圆半径 */
        private int outerCircleRadius;
        /** 内圆半径 */
        private int innerCircleRadius;
        private int innerTransRadius;
        /** 未选中颜色 */
        private int normalColor;
        /** 选中颜色 */
        private int checkedColor;
        /**
         * 每个单元个宽度
         */
        private int unitWidth;
        private Path linePath;
        private float curX, curY;
        /** 解锁密码数字字符串,默认密码123456 */
        private String password = "123456";
        private OnUnlockListener onUnlockListener;
    
        public UnlockNineSquaresView(Context context) {
            super(context);
            init();
        }
    
        public UnlockNineSquaresView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            width = MeasureSpec.getSize(widthMeasureSpec);
    
            unitWidth = width / 6;  //需要固定为width的1/6
            outerCircleRadius = width / 10;
            innerCircleRadius = width / 45;
            innerTransRadius = width / 30;
    
            initDotParams();
    
            setMeasuredDimension(width, width);
        }
    
        private void init() {
            initPaint();
        }
    
        private void initPaint() {
    
            normalColor = getResources().getColor(R.color.blue);
            checkedColor = getResources().getColor(R.color.deep_blue);
    
            circlePaint = new Paint();
            circlePaint.setColor(normalColor);
            circlePaint.setStyle(Paint.Style.STROKE);
            circlePaint.setStrokeWidth(3);
            circlePaint.setAntiAlias(true);
    
            innerDotPaint = new Paint();
            innerDotPaint.setColor(normalColor);
            innerDotPaint.setAntiAlias(true);
    
            transparentPaint = new Paint();
            transparentPaint.setColor(getResources().getColor(R.color.trans_blue));
            transparentPaint.setAntiAlias(true);
    
            linePaint = new Paint();
            linePaint.setColor(checkedColor);
            linePaint.setStyle(Paint.Style.STROKE);
            linePaint.setStrokeWidth(6);
            linePaint.setAntiAlias(true);
            linePath = new Path();
        }
    
        /**
         * 设置各个dot的位置
         */
        private void initDotParams() {
            drawDots.clear();
            dots.clear(); //重复调用onMeasure需要重置dots
    
            //根据行列值来设置当前横纵坐标 (j,i)
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    //i表行数,j表列数,当前为第i行j列位置
                    int left = getLeft() + (j * 2 + 1) * unitWidth;
                    int top = getTop() + (i * 2 + 1) * unitWidth;
                    dots.add(new Dot(left, top));
                }
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                    collisionDetection(event.getX(), event.getY());
                    break;
                case MotionEvent.ACTION_UP:
                    resetState();
                    break;
            }
    
            postInvalidate();
            return true;
        }
    
        /**
         * 实时与每个圆碰撞检测
         * @param curX 当前触摸x位置
         * @param curY 当前触摸y位置
         */
        private void collisionDetection(float curX, float curY) {
            if (drawDots.size() == password.length()) {  //输完密码结束碰撞检测
                return;
            }
    
            this.curX = curX;
            this.curY = curY;
    
            for (int i=0;i<dots.size();i++) {
                //遍历每个圆判断当前触摸点是否在某个圆之内
                double difX = Math.pow(dots.get(i).x - curX, 2);
                double difY = Math.pow(dots.get(i).y - curY, 2);
                if (Math.sqrt(difX + difY) <= outerCircleRadius) {
                    //触摸点与圆心距离小于半径,即判断为触摸点在圆内
    
                    if (!drawDots.contains(i)) { //避免重复添加
                        drawDots.add(i);
                    }
    
                    checkPsdCorrect();
                }
            }
        }
    
        /**
         * 校验密码是否正确
         */
        private void checkPsdCorrect() {
            if (drawDots.size() != password.length()) {
                return;
            }
    
            //输完密码,去验证密码是否正确
            StringBuilder stringBuilder = new StringBuilder();
            for (int num : drawDots) {
                stringBuilder.append(num+1);
            }
    
            if (stringBuilder.toString().equals(password)) { //stringBuilder.toString()为当前输入密码
                if (onUnlockListener != null) {
                    onUnlockListener.unlockSuccess();
                }
            } else {
                if (onUnlockListener != null) {
                    onUnlockListener.unlockFail();
                    virate();
                }
            }
        }
    
        /**
         * 解锁失败调用一次震动
         */
        private void virate() {
            Vibrator vibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
            long [] pattern = {100,400,100,400};
            vibrator.vibrate(pattern,-1);
        }
    
        /**
         * 每次抬手需要重置最初状态,即未输密码状态
         */
        private void resetState() {
            drawDots.clear();  //选中圆清除
            linePath.reset();  //连线清除
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            circlePaint.setColor(getResources().getColor(R.color.blue));
            innerDotPaint.setColor(getResources().getColor(R.color.blue));
            for (int i = 0; i < DOT_COUNT; i++) {
                canvas.drawCircle(dots.get(i).x, dots.get(i).y, outerCircleRadius, circlePaint);  //画每个dot的外圆
    
                //画每个dot的内圆
                canvas.drawCircle(dots.get(i).x, dots.get(i).y, innerCircleRadius, innerDotPaint);  //画每个dot的内圆
            }
    
            //path移动到第一个点
            int curPos;
            if (drawDots.iterator().hasNext()) {
                curPos = drawDots.iterator().next();
                linePath.moveTo(dots.get(curPos).x, dots.get(curPos).y);
            }
    
            circlePaint.setColor(getResources().getColor(R.color.deep_blue));
            innerDotPaint.setColor(getResources().getColor(R.color.deep_blue));
    
            //对已经存储的点按照顺序连线
            for (Integer drawDot : drawDots) {
                linePath.lineTo(dots.get(drawDot).x, dots.get(drawDot).y);
    
                canvas.drawCircle(dots.get(drawDot).x, dots.get(drawDot).y, outerCircleRadius, circlePaint);  //画每个dot的外圆
                canvas.drawCircle(dots.get(drawDot).x, dots.get(drawDot).y, innerCircleRadius, innerDotPaint);  //画每个dot的内圆
                canvas.drawCircle(dots.get(drawDot).x, dots.get(drawDot).y, innerTransRadius, transparentPaint);  //画每个dot的透明圆
            }
    
            canvas.drawPath(linePath, linePaint);
    
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        /**
         * 每格圆形类
         */
        class Dot {
            private int x;
            private int y;
    
            public Dot(int x, int y) {
                this.x = x;
                this.y = y;
            }
        }
    
        public void setOnUnlockListener(OnUnlockListener onUnlockListener) {
            this.onUnlockListener = onUnlockListener;
        }
    
        interface OnUnlockListener {
            void unlockSuccess();
    
            void unlockFail();
        }
    }
    
    

    MainActivity调用:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            final TextView tvResult = findViewById(R.id.tv_show_result);
            TextView tvTips = findViewById(R.id.tv_tips);
            tvTips.setText("当前密码为Z字型");
    
            UnlockNineSquaresView unlockNineSquaresView = findViewById(R.id.unlockview);
            unlockNineSquaresView.setPassword("1235789");
    
            unlockNineSquaresView.setOnUnlockListener(new UnlockNineSquaresView.OnUnlockListener() {
                @Override
                public void unlockSuccess() {
                    tvResult.setText("密码正确");
                }
    
                @Override
                public void unlockFail() {
                    tvResult.setText("密码错误");
                }
            });
        }
    }
    

    后续优化点:

    在今后的优化中,还是朝着选中状态的优化,密码输入检验更完善,密码校验成功变绿色,失败变红色。然后对代码进行封装,实现更多自定义属性方法使用,这些方面进行持续优化。

    相关文章

      网友评论

        本文标题:Android九宫格解锁自定义view

        本文链接:https://www.haomeiwen.com/subject/wlodxktx.html