红橙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

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置