Android九宫格解锁自定义view

很早以前就想自己写一个九宫格解锁了,现在终于一鼓作气安排上了,难度在自定义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 dots = new ArrayList<>();
    /**
     * 按照顺序记录需要连线的dot的序号,使用LinkedHashSet从而更满足有序不重复的特点
     */
    private LinkedHashSet 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

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)