红橙Darren视频笔记 九宫格解锁 Java版 & IntDef使用

参考链接
https://www.jianshu.com/p/74e760ef8d10
花了接近一天 终于完工
最终效果:
在这里插入图片描述
九宫格看起来复杂 将步骤分解 其实不是很复杂
本文先讲思路 后贴代码

一 定义结构体 测量宽高

结构体Point用于记录各个点 点的位置 index用于记录密码 status记录按下的状态
覆盖onMeasure方法 取宽高中的较小者 绘制一个正方形

二 绘制默认状态的9个圆圈

我们需要一个画笔以及各个圆圈的大小 位置等信息来绘制,因此先做第三步。完成第三步继续做这一步,需要考虑内圆和外圆的半径,可以先随便指定大小,然后根据美观度调整值,当然这是在没有设计的情况下。

三 初始化圆和画笔

需要先计算9个圆圈的位置 用二维数组记录位置以及状态。完成第四步就可以接着初始化圆和画笔了,需要准备三种颜色的画笔

四 计算9个圆圈的位置

一开始我还搞错了圆圈的位置 错误思路:
计算x坐标
第二列的x坐标都是viewWidth/2
第一列的x坐标就是viewWidth/4
第三列的x坐标就是viewWidth3/4
计算y坐标
计算过x坐标后发现 y坐标和x坐标几乎一样就是位置换了 注意前提 viewHeight=viewWidth
第二行的圆的x坐标都是viewWidth/2+dy (viewHeight/2 )第一行的x坐标就是viewWidth/4+dy (viewHeight/4 )第三行的x坐标就是viewWidth
3/4+dy(viewHeight*3/4)
dy=(screenHeight-viewHeight)/2
注意:因为这里用到了view的宽高 所以初始化圆的时候 应该是能拿到宽高的时刻,因此至少要在onMesure方法之后

在这里插入图片描述
之前的计算有误 如果按照我之前的思路写 最终效果:
在这里插入图片描述
出现的问题有两个
1 所有的圆都偏下
这是因为我在第一步重写了onMeasure方法 因此就不再需要+dy了
2 第一列和第三列的圆心错误
我一开始只是简单认为 等分屏幕即可 但是我忽略了中间圆的直径
明显1/4 viewWidth不够作为间距 那么调整变化值 调整多少合适呢,假如各个圆之间的距离等于半径那么有下图
在这里插入图片描述
很明显 屏幕可以分割为10等分
所以
计算x坐标
第二列的x坐标都是viewWidth/2
第一列的x坐标就是viewWidth2/10
第三列的x坐标就是viewWidth
8/10
y坐标省略

五 根据状态绘制不同的圆圈

六 根据手指的状态修改各个圆的状态

判断手指是不是在圆内 初中数学知识 判断点到圆心的距离是否大于半径
在按下和move的时候 得到手指的x和y坐标,遍历9个圆,判断点是否在圆内 如果在,则将其状态更改为pressed
修改绘制圆圈的方法 让其根据圆的状态绘制

七 画线

7.1 绘制两个圆圈之间的连线

使用一个数组保存按下状态的圆圈,更新数组的时机可以放在检查手指是否在圆中的时候,当数组数目大于1时开始绘制,绘制时机可放在画圆之后
绘制时取列表的两个圆的圆心绘制线 但是如果从圆心绘制 效果不是很好
在这里插入图片描述
我们要从内圆的边缘开始绘制 这里又涉及一点数学知识了
在这里插入图片描述
我们已知x y radius求 dx dy
dx/x= radius/两圆心距离
dx = radius/两圆心距离x
那么 dy = radius/两圆心距离
y
计算完毕之后还要考虑一下是+dx dy还是-dx dy

7.2 绘制手与最后一个圆圈之间的连线

有了上面的计算 这个相对简单了 起点的计算仍然需要用到上面的算法
注意如果选中的圆数目为0 不需要绘制
取最后一个选择的点 绘制到手指的线 注意把第7.1中第二个点改为手指的坐标 并且drawLine的终点不需要加或者减dx dy了
注意手指如果在内圈 不要绘制线

7.3 松开手指 去掉最后到手指的线

可以在action up的将手指坐标清空 然后在绘制最后一个选择的圆与手指的线的时候判断手指的坐标是否存在

八 判断手势是否正确

在主界面传入正确的密码
在action up的时候check密码
如果密码太短 弹出提示
如果密码错误 弹出提示
如果密码正确 弹出提示
且提示出现是 屏幕锁住(设置标志位 屏蔽touch事件) 不让用户继续滑动

九 部分代码

自定义view

public class LockPatternView extends View {
    private Point[][] mPoints;
    private boolean hasInit = false;
    private Paint mPaintNormal, mPaintPressed, mPaintError;//这里就不搞那么多画笔了 内外圈一样的颜色
    private int mRadiusOut, mRadiusInner;
    private ArrayList<Point> mPressedPoints = new ArrayList<>();
    private PassListener mPassListener;
    private float mFingerX = -1;
    private float mFingerY = -1;
    private boolean mNeedIntercept = false;

    public LockPatternView(Context context) {
        super(context);
    }

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

    public LockPatternView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //在这里写死 采用宽高中较短的那个作为边,绘制区域为这个边长的正方形
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int smaller = Math.min(height, width);
        setMeasuredDimension(smaller, smaller);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!hasInit) {//只初始化一次
            hasInit = true;
            initPoints();//初始化圆 以及位置
            intPaint();//初始化画笔
        }
        drawCircle(canvas);
        drawLine(canvas);
        drawLine2Finger(canvas);
    }

    private void drawLine2Finger(Canvas canvas) {
        //注意如果选中的圆数目为0 不需要绘制
        if (mPressedPoints.size() == 0) {
            return;
        }
        //手指离开屏幕 直接return
        if (mFingerX < 0 || mFingerY < 0) {
            return;
        }

        //手指在内圈 直接return
        if (isFingerInCircle(mFingerX, mFingerY, mPressedPoints.get(mPressedPoints.size() - 1).positionX, mPressedPoints.get(mPressedPoints.size() - 1).positionY)) {
            return;
        }

        double x = mPressedPoints.get(mPressedPoints.size() - 1).positionX - mFingerX;
        double y = mPressedPoints.get(mPressedPoints.size() - 1).positionY - mFingerY;
        double centerDistance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        //dx = radius/两圆心距离*x
        double dx = mRadiusInner / centerDistance * x;
        //int dy =  radius/两圆心距离*y
        double dy = mRadiusInner / centerDistance * y;
        canvas.drawLine((float) (mPressedPoints.get(mPressedPoints.size() - 1).positionX - dx), (float) (mPressedPoints.get(mPressedPoints.size() - 1).positionY - dy), mFingerX, mFingerY, mPaintPressed);
    }

    private void drawLine(Canvas canvas) {
        if (mPressedPoints.size() < 2) {
            return;
        }
        for (int i = 1; i < mPressedPoints.size(); i++) {
            //绘制前一个圆和当前的圆的连线
            //注意符号 不要调用绝对值函数了 因为手势可以从左上向右下 也可以反过来,这时是+dx dy还是-dx dy值得考虑
            double x = mPressedPoints.get(i - 1).positionX - mPressedPoints.get(i).positionX;
            double y = mPressedPoints.get(i - 1).positionY - mPressedPoints.get(i).positionY;
            double centerDistance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
            //dx = radius/两圆心距离*x
            double dx = mRadiusInner / centerDistance * x;
            //int dy =  radius/两圆心距离*y
            double dy = mRadiusInner / centerDistance * y;
            if (mPressedPoints.get(i).status == PointStatus.NORMAL) {
                return;
            }
            canvas.drawLine((float) (mPressedPoints.get(i - 1).positionX - dx), (float) (mPressedPoints.get(i - 1).positionY - dy), (float) (mPressedPoints.get(i).positionX + dx), (float) (mPressedPoints.get(i).positionY + dy),
                    mPressedPoints.get(i).status == PointStatus.PRESSED ? mPaintPressed : mPaintError);
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mNeedIntercept) {
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mFingerX = event.getX();
                mFingerY = event.getY();
                traversingCheckInPoints(mFingerX, mFingerY);
                return true;
            case MotionEvent.ACTION_MOVE:
                mFingerX = event.getX();
                mFingerY = event.getY();
                traversingCheckInPoints(mFingerX, mFingerY);
                break;
            case MotionEvent.ACTION_UP:
                mNeedIntercept = true;
                mFingerX = -1;
                mFingerY = -1;
                //checkPassWord(); 检查密码应该交由外部比较
                StringBuilder passBuffer = new StringBuilder();//拼凑密码
                for (Point point : mPressedPoints) {
                    passBuffer.append(point.index);
                }
                mPassListener.notifyPass(passBuffer.toString());
                //抬起手指后两秒无法操作

                postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mNeedIntercept = false;
                    }
                }, 2000);
                break;
        }
        invalidate();
        return super.onTouchEvent(event);
    }

    //不应该在控件内部判断密码 维持控件的纯洁度
//    private void checkPassWord() {
//        mNeedIntercept = true;
//        clearStatusDelay(2000);
//        //如果密码太短 弹出提示 并将所有点状态跟新为error 设置屏幕无法触碰 两秒后清空状态
//        if (mPressedPoints.size() <= 4) {
//            Toast.makeText(getContext(), "密码太短!", Toast.LENGTH_SHORT).show();
//            for (Point point : mPressedPoints) {
//                point.setStatus(PointStatus.ERROR);
//            }
//            return;
//        }
//
//        StringBuffer passBuffer = new StringBuffer();//拼凑密码
//        for (Point point : mPressedPoints) {
//            passBuffer.append(point.index);
//        }
//        //如果密码正确 弹出提示 设置屏幕无法触碰 两秒之后清空状态
//        Log.d("TAG", "checkPassWord: mPassWord " + mPassWord + " passBuffer " + passBuffer);
//        if (passBuffer.toString().equals(mPassWord)) {
//            Toast.makeText(getContext(), "密码正确!", Toast.LENGTH_SHORT).show();
//        } else {//如果密码错误 弹出提示 并将所有点状态跟新为error 设置屏幕无法触碰 两秒后清空状态
//            Toast.makeText(getContext(), "密码错误!", Toast.LENGTH_SHORT).show();
//            for (Point point : mPressedPoints) {
//                point.setStatus(PointStatus.ERROR);
//            }
//        }
//    }

    private void drawCircle(Canvas canvas) {
        for (Point[] pointArr : mPoints) {
            for (Point point : pointArr) {
                switch (point.getStatus()) {
                    case PointStatus.NORMAL:
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusOut, mPaintNormal);
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusInner, mPaintNormal);
                        break;
                    case PointStatus.PRESSED:
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusOut, mPaintPressed);
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusInner, mPaintPressed);
                        break;
                    case PointStatus.ERROR:
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusOut, mPaintError);
                        canvas.drawCircle(point.positionX, point.positionY, mRadiusInner, mPaintError);
                        break;
                }
            }
        }
    }

    private void intPaint() {
        mRadiusOut = getMeasuredWidth() / 10;
        mRadiusInner = mRadiusOut / 4;
        mPaintNormal = getPaintByColor(0xFFFFFFFF);
        mPaintPressed = getPaintByColor(0xFFCCFFFF);
        mPaintError = getPaintByColor(0xFFCC3333);
    }

    private Paint getPaintByColor(int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        return paint;
    }

    private void initPoints() {
        mPoints = new Point[3][3];
        //第一行 每一行y坐标一样
        mPoints[0][0] = new Point(getMeasuredWidth() / 5, getMeasuredWidth() / 5, 0, PointStatus.NORMAL);
        mPoints[0][1] = new Point(getMeasuredWidth() * 2 / 4, getMeasuredWidth() / 5, 1, PointStatus.NORMAL);
        mPoints[0][2] = new Point(getMeasuredWidth() * 4 / 5, getMeasuredWidth() / 5, 2, PointStatus.NORMAL);
        //第二行
        mPoints[1][0] = new Point(getMeasuredWidth() / 5, getMeasuredWidth() / 2, 3, PointStatus.NORMAL);
        mPoints[1][1] = new Point(getMeasuredWidth() * 2 / 4, getMeasuredWidth() / 2, 4, PointStatus.NORMAL);
        mPoints[1][2] = new Point(getMeasuredWidth() * 4 / 5, getMeasuredWidth() / 2, 5, PointStatus.NORMAL);
        //第三行
        mPoints[2][0] = new Point(getMeasuredWidth() / 5, getMeasuredWidth() * 4 / 5, 6, PointStatus.NORMAL);
        mPoints[2][1] = new Point(getMeasuredWidth() * 2 / 4, getMeasuredWidth() * 4 / 5, 7, PointStatus.NORMAL);
        mPoints[2][2] = new Point(getMeasuredWidth() * 4 / 5, getMeasuredWidth() * 4 / 5, 8, PointStatus.NORMAL);
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({PointStatus.NORMAL, PointStatus.PRESSED, PointStatus.ERROR})
    public @interface PointStatus {
        int NORMAL = 0;
        int PRESSED = 1;
        int ERROR = 2;
    }

    void traversingCheckInPoints(float fingerX, float fingerY) {
        for (Point[] pointArr : mPoints) {
            for (Point point : pointArr) {
                if (isFingerInCircle(fingerX, fingerY, point.positionX, point.positionY)) {
                    point.setStatus(PointStatus.PRESSED);
                    if (!mPressedPoints.contains(point)) {
                        mPressedPoints.add(point);
                    }
                    //Log.d("TAG", "traversingCheckInPoints: in!!");
                    return;
                }
            }
        }
    }

    boolean isFingerInCircle(float fingerX, float fingerY, int circleX, int circleY) {//初中知识 判断点到圆心的距离
        //Log.d("TAG", "traversingCheckInPoints: fingerX->"+fingerX+" fingerY "+fingerY+" circleX "+circleX+" circleY "+circleY);
        float dx = Math.abs(fingerX - circleX);
        float dy = Math.abs(fingerY - circleY);
        return Math.sqrt(dx * dx + dy * dy) - mRadiusOut < 0;
    }

    static class Point {
        int positionX;
        int positionY;
        int index;
        @PointStatus
        int status;

        Point(int positionX, int positionY, int index, @PointStatus int status) {
            this.positionX = positionX;
            this.positionY = positionY;
            this.index = index;
            this.status = status;
        }

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

        int getStatus() {
            return status;
        }
    }

    public void setPointsStatus(@PointStatus int pointsStatus) {
        for (LockPatternView.Point point : mPressedPoints) {
            point.setStatus(pointsStatus);
        }
        invalidate();
    }

    public void resetLockPatternView() {
        setPointsStatus(PointStatus.NORMAL);
        mPressedPoints.clear();
    }

    public void setPassListener(PassListener mPassListener) {
        this.mPassListener = mPassListener;
    }

    interface PassListener {
        void notifyPass(String pass);
    }
}

activity

public class MainActivity extends AppCompatActivity {
    String mPassWord = "123456";
    LockPatternView mLockPattern;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLockPattern = findViewById(R.id.lockPattern);
        mLockPattern.setPassListener(new LockPatternView.PassListener() {
            @Override
            public void notifyPass(String pass) {
                checkPassWord(pass);
            }
        });
    }

    private void checkPassWord(String passWord) {
        //如果密码太短 弹出提示 并将所有点状态跟新为error 设置屏幕无法触碰 两秒后清空状态
        if (passWord.length() <= 4) {
            Toast.makeText(MainActivity.this, "密码太短!", Toast.LENGTH_SHORT).show();
            mLockPattern.setPointsStatus(LockPatternView.PointStatus.ERROR);
            delayResetLockPattern(2000);
            //2s后恢复正常状态
            return;
        }

        //如果密码正确 弹出提示 设置屏幕无法触碰 两秒之后清空状态
        Log.d("TAG", "checkPassWord: mPassWord " + mPassWord + " passWord " + passWord);
        if (passWord.equals(mPassWord)) {
            Toast.makeText(MainActivity.this, "密码正确!", Toast.LENGTH_SHORT).show();
            delayResetLockPattern(2000);
        } else {//如果密码错误 弹出提示 并将所有点状态跟新为error 设置屏幕无法触碰 两秒后清空状态
            Toast.makeText(MainActivity.this, "密码错误!", Toast.LENGTH_SHORT).show();
            delayResetLockPattern(2000);
        }
    }

    void delayResetLockPattern(int delayMillisecond) {
        mLockPattern.postDelayed(new Runnable() {
            @Override
            public void run() {
                mLockPattern.resetLockPatternView();
            }
        }, delayMillisecond);
    }
}

activity布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.lockpatternview.LockPatternView
        android:id="@+id/lockPattern"
        android:layout_centerInParent="true"
        android:background="#ccc"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

全代码:
https://github.com/caihuijian/learn_darren_android.git
flag:还缺少绘制三角形的部分,没看懂 等日后看懂再补

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值