9宫格图形解锁的操作就是在9个点上进行的,所以我们先定义一个点类,该类主要用于存储点的信息:坐标,状态,位置。

public class Point {//正常状态public static int STATE_NORMAL = 0;//选中状态public static int STATE_PRESSED = 1;//错误状态public static int STATE_ERROR = 2;public float x, y;public int index = 0, status = 0;public Point() {}public Point(float x, float y) {this.x = x;this.y = y;}
}

自定义的View
View的初始化先绘制9个点,点有三个状态正常,选中,错误,对应的图片资源可自由更改。并创建二维数组存储9个点的位置。
onTouchEvent事件中,监听DOWN,MOVE,UP三个状态,判断起始点是否在9个点上,然后判断触摸移动过程中经过哪些点,是否重复,记录到List中。
触摸过程中使用postInvalidate()刷新View,根据List中的点绘制点与点之间的连线。
触摸结束后,根据点的位置,判读点的个数是否足够,密码是否正确等,使用handler发送消息postInvalidate()刷新View,重新绘制点和线的样式,返回回调结果。

ps:mPassword变量为图形解锁的密码,需要提前赋值,默认为null。
public class GraphicLockView extends View {/*** 图形解锁的密码* 解锁前,请给该变量赋值为你的密码*/public static String mPassword;private Point[][] points = new Point[3][3];  //创建一个3行3列的点数组private boolean isInit;   //判断有没有初始化private boolean isSelect;  //判断手指第一次按下屏幕有没有选中点private boolean isFinishMove;   //表示一次完整的图案绘制是否结束private boolean isMoveButNotPoint;   //表示手指在移动,但是并不是九宫格中的点private float width, height;   //屏幕宽高private static final int MIN_POINT = 4;    //最小能构成密码的点数private float offsetsX, offsetsY; //偏移量(在这里偏移量等于大边减去小边再除以2)private float bitmapR;   //图片资源的半径private float moveX, moveY;  //手势移动的x,y坐标private Bitmap bpPointNormal, bpPointPressed, bpPointError;  //点的三种图片private Bitmap bpLinePressed, bpLineError;  //线的三种图片private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);private List<Point> selectPointList = new ArrayList<>();   //储存按下的点的集合private Matrix matrix = new Matrix();  //矩阵,用来处理线的缩放private OnGraphicLockListener onGraphicLockListener;   //对外的监听器public GraphicLockView(Context context) {super(context);}public GraphicLockView(Context context, AttributeSet attrs) {super(context, attrs);}public GraphicLockView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public void setOnGraphicLockListener(OnGraphicLockListener onGraphicLockListener) {this.onGraphicLockListener = onGraphicLockListener;}@Overrideprotected void onDraw(Canvas canvas) {//初始化,只执行一次即可if (!isInit) {//初始化点initPoints();}//绘制——将点绘制到画布上pointToCanvas(canvas);//绘制刷新时,根据点绘制连线if (selectPointList.size() > 0) {Point startPoint = selectPointList.get(0);//绘制九宫格坐标里的点for (int i = 0; i < selectPointList.size(); i++) {Point endPoint = selectPointList.get(i);lineToCanvas(canvas, startPoint, endPoint);startPoint = endPoint;}//绘制九宫格坐标以外的点if (isMoveButNotPoint) {lineToCanvas(canvas, startPoint, new Point(moveX, moveY));}}}/*** 初始化点*/private void initPoints() {//1.根据屏幕尺寸,计算偏移量width = getWidth();height = getHeight();if (width > height) {   //横屏//横屏时只有x坐标有偏移量offsetsX = (width - height) / 2;width = height;} else {   //竖屏//竖屏时只有y坐标有偏移量offsetsY = (height - width) / 2;height = width;}//2。指定资源文件bpPointNormal = BitmapFactory.decodeResource(getResources(), R.drawable.point_normal);bpPointPressed = BitmapFactory.decodeResource(getResources(), R.drawable.point_pressed);bpPointError = BitmapFactory.decodeResource(getResources(), R.drawable.point_error);bpLinePressed = BitmapFactory.decodeResource(getResources(), R.drawable.line_pressed);bpLineError = BitmapFactory.decodeResource(getResources(), R.drawable.line_error);//3.点的坐标//第一排points[0][0] = new Point(offsetsX + width / 4, offsetsY + height / 4);points[0][1] = new Point(offsetsX + width / 2, offsetsY + height / 4);points[0][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 4);//第二排points[1][0] = new Point(offsetsX + width / 4, offsetsY + height / 2);points[1][1] = new Point(offsetsX + width / 2, offsetsY + height / 2);points[1][2] = new Point(offsetsX + width - width / 4, offsetsY + height / 2);//第三排points[2][0] = new Point(offsetsX + width / 4, offsetsY + height - height / 4);points[2][1] = new Point(offsetsX + width / 2, offsetsY + height - height / 4);points[2][2] = new Point(offsetsX + width - width / 4, offsetsY + height - height / 4);//4.计算图片资源的半径bitmapR = bpPointNormal.getWidth() / 2;//5.设置密码按键,初始化每个点,设置为1——9int index = 1;for (int i = 0; i < points.length; i++) {for (int j = 0; j < points[i].length; j++) {points[i][j].index = index;index++;}}//初始化完成isInit = true;}@Overridepublic boolean onTouchEvent(MotionEvent event) {moveX = event.getX();moveY = event.getY();isFinishMove = false;isMoveButNotPoint = false;Point point = null;switch (event.getAction()) {case MotionEvent.ACTION_DOWN://每次手指按下的时候都表示重新绘制图案resetPoint();//检查当前按下的点与九宫格中的九个点是否吻合point = checkSelectPoint();if (point != null) {isSelect = true;}break;case MotionEvent.ACTION_MOVE:if (isSelect) {point = checkSelectPoint();if (point == null) {isMoveButNotPoint = true;}}break;case MotionEvent.ACTION_UP:isFinishMove = true;isSelect = false;break;}//选中重复检查if (!isFinishMove && isSelect && point != null) {if (checkCrossPoint(point)) { //交叉点isMoveButNotPoint = true;} else {//非交叉点(新的点)point.status = Point.STATE_PRESSED;selectPointList.add(point);}}//绘制结束if (isFinishMove && onGraphicLockListener != null) {//绘制不成立if (selectPointList.size() == 1) {resetPoint();} else if (selectPointList.size() < MIN_POINT && selectPointList.size() > 0) { //绘制错误,点不够onGraphicLockListener.LockShort();errorPoint();} else if (selectPointList.size() >= MIN_POINT) {//绘制成功StringBuilder strPassword = new StringBuilder();for (Point pwdPoint : selectPointList) {strPassword.append(pwdPoint.index);}if (TextUtils.isEmpty(mPassword)) {onGraphicLockListener.LockSuccess(0, strPassword.toString());correctPoint();} else {if (mPassword.equals(strPassword.toString())) {onGraphicLockListener.LockSuccess(1, strPassword.toString());correctPoint();} else {onGraphicLockListener.LockFailure();errorPoint();}}}}//刷新view,会调用onDraw方法postInvalidate();return true;}/*** 检查交叉点** @param point 点* @return 是否交叉*/private boolean checkCrossPoint(Point point) {return selectPointList.contains(point);}/*** 设置绘制不成立*/public void resetPoint() {//将点的状态还原for (Point point : selectPointList) {point.status = Point.STATE_NORMAL;}selectPointList.clear();}/*** 设置绘制错误,将点的状态还原*/public void errorPoint() {for (Point point : selectPointList) {point.status = Point.STATE_ERROR;}new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage(0x001);}}).start();}/*** 设置绘制成功,将点的状态还原*/private void correctPoint() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage(0x001);}}).start();}private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 0x001) {for (Point point : selectPointList) {point.status = Point.STATE_NORMAL;}selectPointList.clear();postInvalidate();}}};private Point checkSelectPoint() {for (int i = 0; i < points.length; i++) {for (int j = 0; j < points[i].length; j++) {Point point = points[i][j];if (LockUtil.isCoincide(point.x, point.y, bitmapR, moveX, moveY)) {return point;}}}return null;}/*** 将点绘制到画布上** @param canvas 画布*/private void pointToCanvas(Canvas canvas) {//遍历点的集合for (int i = 0; i < points.length; i++) {for (int j = 0; j < points[i].length; j++) {Point point = points[i][j];if (points[i][j].status == Point.STATE_PRESSED) {canvas.drawBitmap(bpPointPressed, point.x - bitmapR, point.y - bitmapR, mPaint);} else if (points[i][j].status == Point.STATE_ERROR) {canvas.drawBitmap(bpPointError, point.x - bitmapR, point.y - bitmapR, mPaint);} else {canvas.drawBitmap(bpPointNormal, point.x - bitmapR, point.y - bitmapR, mPaint);}}}}/*** 将线绘制到画布上** @param canvas     画布* @param startPoint 开始的点* @param endPoint   结束的点*/private void lineToCanvas(Canvas canvas, Point startPoint, Point endPoint) {float lineLength = (float) LockUtil.twoPointDistance(startPoint, endPoint);float degree = LockUtil.getDegrees(startPoint, endPoint);canvas.rotate(degree, startPoint.x, startPoint.y);  //旋转if (startPoint.status == Point.STATE_PRESSED) {  //按下的状态//设置线的缩放比例,在这里线是往一个方向缩放的,即x轴,我们只需要设置x轴的缩放比例即可,y轴默认为1matrix.setScale(lineLength / bpLinePressed.getWidth(), 1);matrix.postTranslate(startPoint.x - bpLinePressed.getWidth() / 2, startPoint.y - bpLinePressed.getHeight() / 2);canvas.drawBitmap(bpLinePressed, matrix, mPaint);} else if (startPoint.status == Point.STATE_ERROR) {//错误的状态matrix.setScale(lineLength / bpLineError.getWidth(), 1);matrix.postTranslate(startPoint.x - bpLineError.getWidth() / 2, startPoint.y - bpLineError.getHeight() / 2);canvas.drawBitmap(bpLineError, matrix, mPaint);}canvas.rotate(-degree, startPoint.x, startPoint.y);  //把旋转的角度转回来}/*** 图案监听器*/public interface OnGraphicLockListener {void LockSuccess(int what, String password);//what=0设置密码成功回调;what=1解锁成功回调void LockFailure();//解锁失败的回调void LockShort();//密码太短的回调}
}

View中使用的方法

public class LockUtil {/*** 两点之间的距离** @param a 第一个点* @param b 第二个点* @return 距离*/public static double twoPointDistance(Point a, Point b) {//x轴差的平方加上y轴的平方,然后取平方根return Math.sqrt(Math.abs(a.x - b.x) * Math.abs(a.x - b.x) + Math.abs(a.y - b.y) * Math.abs(a.y - b.y));}/*** 判断手指移动的坐标与点的坐标是否重合(默认小于点半径的一半的时候表示重合)** @param pointX 点横坐标* @param pointY 点纵坐标* @param r      点半径* @param moveX  手指移动的横坐标* @param moveY  手指移动的纵坐标* @return*/public static boolean isCoincide(float pointX, float pointY, float r, float moveX, float moveY) {return Math.sqrt((pointX - moveX) * (pointX - moveX) + (pointY - moveY) * (pointY - moveY)) < r;}/*** 获取角度** @param pointA 第一个点* @param pointB 第二个点* @return*/public static float getDegrees(Point pointA, Point pointB) {return (float) Math.toDegrees(Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x));}
}

设置图形密码
初始的图形密码为null,将第一次绘制成功的图形密码赋值给GraphicLockView.mPassword,作为再次绘制验证的初始密码。再次绘制成功后,使用SharedPreferences存储图形密码。

public class SettingActivity extends AppCompatActivity {private GraphicLockView graphicLockView;private TextView text;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);text = findViewById(R.id.tv_text);text.setText("绘制解锁图案");graphicLockView = findViewById(R.id.lock_view);graphicLockView.setOnGraphicLockListener(new GraphicLockView.OnGraphicLockListener() {@Overridepublic void LockSuccess(int what, String password) {if (what == 0) {GraphicLockView.mPassword = password;text.setText("再次绘制解锁图案");} else {SPUtil.put(SettingActivity.this, "PASSWORD", password);SettingActivity.this.finish();}}@Overridepublic void LockFailure() {text.setText("与上次绘制的不一致");}@Overridepublic void LockShort() {text.setText("最少连接4个点");}});}
}

解锁图形密码
图形密码设置成功后,该密码就是下次解锁的密码。根据回调结果做判断,进行相应的逻辑处理即可。

public class UnlockActivity extends AppCompatActivity {private GraphicLockView graphicLockView;private TextView text;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);text = findViewById(R.id.tv_text);text.setText("请输入手势密码");graphicLockView = findViewById(R.id.lock_view);graphicLockView.setOnGraphicLockListener(new GraphicLockView.OnGraphicLockListener() {@Overridepublic void LockSuccess(int what, String password) {text.setText("解锁成功");}@Overridepublic void LockFailure() {text.setText("密码错误");}@Overridepublic void LockShort() {text.setText("最少连接4个点");}});}
}

重置密码
清除SharedPreferences中密码对应的key或者直接将密码的key赋值为空,即可。

PHP直播平台源码Android自定义View:9宫格图形手势解锁相关推荐

  1. 直播平台源码搭建教程之Android音视频开发

    直播平台源码搭建教程之Android音视频开发 音频 将声音保存成音频的过程,其实就是将模拟音频数字化的过程,为了实现这个过程,就需要对模拟音频进行采样.量化和编码.接下来我们详细讲解这一过程. 采样 ...

  2. 直播系统源码App中Android酷炫礼物动画直播平台源码搭建教程(上篇)

    直播系统源码App中Android酷炫礼物动画直播平台源码搭建教程(上篇) 在当下移动直播火爆的年代,如果你曾经使用过移动端直播应用,相信会被里面那令人惊叹的礼物动画效果迷住,比如像下面这样的效果. ...

  3. 手把手带你快速实现直播平台源码聊天室

    工具要求: (1) Android Studio 3.2或更高版本. (2) SDK targetVersion至少为26. 本文直播平台源码聊天室是集成环信IM SDK实现聊天功能,及实现发送礼物. ...

  4. php直播平台源码基于 Nginx 搭建(rtmp、http)直播服务器

    php直播平台源码基于 Nginx 搭建(rtmp.http)直播服务器 直播协议介绍 国内常见公开的直播协议有几个:RTMP.HLS.HDL(HTTP-FLV).RTP,我们来逐一介绍. RTMP ...

  5. 直播系统开发,直播平台源码切忌一成不变

    直播过程中流媒体协议的传输大致包含六个环节,分别是采集.预处理.编码.传输.解码.播放等环节,这六个环节的前五个环节都是需要开发者费心的. 直播系统开发时,会在直播平台中内置分类列表,每个直播平台也都 ...

  6. 直播平台源码搭建教程盘点直播技术中的编解码、直播协议、网络传输与简单实现

    直播平台源码搭建教程盘点直播技术中的编解码.直播协议.网络传输与简单实现 Live CheatSheet | 直播技术理论基础与实践概论 音视频直播的基本流程都是采集 → 编码推流 → 网络分发 → ...

  7. 直播平台源码开发 网上为什么那么多人求购直播源码

    不少人都会在网上求购直播平台源码,源码是什么?源码也叫源代码.源程序,就是编写的最原始程序的代码,即程序员工作的"语言",为何那么多人都会求直播源码呢?直播平台源码究竟对直播系统来 ...

  8. 直播平台源码实现状态栏滑动隐藏和tabbar的教程

    现在好多应用的首页都要求状态栏和tabbar支持滑动隐藏,直播平台源码又怎能甘心落后于人呢?对于此类需求,我们可以使用UICollectionView来进行功能实现,UICollectionView ...

  9. 直播平台源码中直播系统捕获音视频的步骤

    如何开发一套完整的直播平台源码,首先需要采集主播的视频和音频功能,然后传入流媒体服务器.本篇主要讲解如何采集主播的视频和音频功能,当前可以切换前置后置摄像头和焦点光标,直播APP拥有独立的美颜SDK, ...

最新文章

  1. # C# 学习笔记(4) 类
  2. vue嵌套数据多层级里面的数据不自动更新渲染dom
  3. python保存至对应目录_python相对目录的基本用法(一)
  4. android中如何新建一个activity,《Android Activity》活动的介绍和创建
  5. python之字典的操作
  6. 字符串 - KMP模式匹配
  7. Java多线程包之BlockingQueue
  8. 使用Jest对原生TypeScript项目进行UI测试
  9. 在线直播系统网站源码搭建一个点播跟直播流媒体服务器
  10. 种子文件转成为磁力链接 下载BT磁力转换小工具
  11. html 轮播图左右切换代码,js实现左右轮播图
  12. 西电计算机学院专硕复试成绩,2018年西安电子科技大学硕士研究生招生统考考生拟录取名单公示...
  13. 子域名收集 -- teemo
  14. 为知笔记打不开 ziw 文件问题
  15. 若依分离版部署遇到的问题
  16. blend2d + MFC
  17. R以逻辑回归为例介绍制作列线图(nomogram)的过程
  18. ESP32(arduino)和声音传感器数据采集并实现连接WiFi进行MQTT通信
  19. 父盒子塌陷问题解决办法
  20. 洛谷 P1088 火星人

热门文章

  1. Mac brew 更换homebrew源为中科大源
  2. Go 工程化标准实践
  3. 比亚迪王传福:无人驾驶就是扯淡,被资本裹挟的一场“皇帝的新装”
  4. 懒人必备公式快速插入word(latexocr+TyporaMathtype)保姆集教程
  5. 京东.青龙面板之xdd-plus搭建教程及问题解决【9.29发布更新】
  6. Shader 特效实例 --- 屏幕后期特效
  7. 看大神如何手工低成本制作一个超级灵活的助焊台!
  8. A20 wifi驱动移植
  9. POI+EasyExcel,简单使用
  10. 买卖基金看那些指标呢?