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

2024-05-30 12:32

本文主要是介绍红橙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);}@Overrideprotected 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);}@Overrideprotected 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;}//手指离开屏幕 直接returnif (mFingerX < 0 || mFingerY < 0) {return;}//手指在内圈 直接returnif (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/两圆心距离*xdouble dx = mRadiusInner / centerDistance * x;//int dy =  radius/两圆心距离*ydouble 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/两圆心距离*xdouble dx = mRadiusInner / centerDistance * x;//int dy =  radius/两圆心距离*ydouble 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);}}@Overridepublic 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() {@Overridepublic 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;@PointStatusint 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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mLockPattern = findViewById(R.id.lockPattern);mLockPattern.setPassListener(new LockPatternView.PassListener() {@Overridepublic 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() {@Overridepublic 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.LockPatternViewandroid: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:还缺少绘制三角形的部分,没看懂 等日后看懂再补

这篇关于红橙Darren视频笔记 九宫格解锁 Java版 IntDef使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1016219

相关文章

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J