轻松实现安卓(Android)九宫格解锁

效果图

思路

首先我们来分析一下实现九宫格解锁的思路:当用户的手指触摸到某一个点时,先判断该点是否在九宫格的某一格范围之内,若在范围内,则该格变成选中的状态;之后用户手指滑动的时候,以该格的圆心为中心,用户手指为终点,两点连线。最后当用户手指抬起时,判断划过的九宫格密码是否和原先的密码匹配。

大致的思路流程就是上面这样的了,下面我们可以来实践一下。

Point 类

我们先来创建一个 Point 类,用来表示九宫格锁的九个格子。除了坐标 x ,y 之外,还有三种模式:正常模式按下模式错误模式。根据模式不同该格子的颜色会有所不同,这会在下面中说明。

public class Point {

 private float x;
 private float y;
 // 正常模式
 public static final int NORMAL_MODE = 1;
 // 按下模式
 public static final int PRESSED_MODE = 2;
 // 错误模式
 public static final int ERROR_MODE = 3;
 private int state = NORMAL_MODE;
 // 表示该格的密码,比如“1”、“2”等
 private String mark;

 public String getMark() {
  return mark;
 }

 public void setMark(String mark) {
  this.mark = mark;
 }

 public Point(float x, float y, String mark) {
  this.x = x;
  this.y = y;
  this.mark = mark;
 }

 public int getState() {
  return state;
 }

 public void setState(int state) {
  this.state = state;
 }

 public float getX() {
  return x;
 }

 public void setX(float x) {
  this.x = x;
 }

 public float getY() {
  return y;
 }

 public void setY(float y) {
  this.y = y;
 }

}

RotateDegrees类

有了上面的 Point 类之后,我们还要创建一个 RotateDegrees 类,主要作用是计算两个 Point 坐标之间的角度

public class RotateDegrees {

 /**
  * 根据传入的point计算出它们之间的角度
  * @param a
  * @param b
  * @return
  */
 public static float getDegrees(Point a, Point b) {
  float degrees = 0;
  float aX = a.getX();
  float aY = a.getY();
  float bX = b.getX();
  float bY = b.getY();

  if (aX == bX) {
   if (aY < bY) {
    degrees = 90;
   } else {
    degrees = 270;
   }
  } else if (bY == aY) {
   if (aX < bX) {
    degrees = 0;
   } else {
    degrees = 180;
   }
  } else {
   if (aX > bX) {
    if (aY > bY) { // 第三象限
     degrees = 180 + (float) (Math.atan2(aY - bY, aX - bX) * 180 / Math.PI);
    } else { // 第二象限
     degrees = 180 - (float) (Math.atan2(bY - aY, aX - bX) * 180 / Math.PI);
    }
   } else {
    if (aY > bY) { // 第四象限
     degrees = 360 - (float) (Math.atan2(aY - bY, bX - aX) * 180 / Math.PI);
    } else { // 第一象限
     degrees = (float) (Math.atan2(bY - aY, bX - aX) * 180 / Math.PI);
    }
   }
  }
  return degrees;
 }

 /**
  * 根据point和(x,y)计算出它们之间的角度
  * @param a
  * @param bX
  * @param bY
  * @return
  */
 public static float getDegrees(Point a, float bX, float bY) {
  Point b = new Point(bX, bY, null);
  return getDegrees(a, b);
 }

}

ScreenLockView 类

然后我们要先准备好关于九宫格的几张图片,比如在九宫格的格子中,NORMAL_MODE 模式下是蓝色的,被手指按住时九宫格的格子是绿色的,也就是对应着上面 Point 类的中 PRESSED_MODE 模式,还有 ERROR_MODE 模式下是红色的。另外还有圆点之间的连线,也是根据模式不同颜色也会不同。在这里我就不把图片贴出来了。

有了图片资源之后,我们要做的就是先在构造器中加载图片:

public class ScreenLockView extends View {

 private static final String TAG = "ScreenLockView";
 // 错误格子的图片
 private Bitmap errorBitmap;
 // 正常格子的图片
 private Bitmap normalBitmap;
 // 手指按下时格子的图片
 private Bitmap pressedBitmap;
 // 错误时连线的图片
 private Bitmap lineErrorBitmap;
 // 手指按住时连线的图片
 private Bitmap linePressedBitmap;
 // 偏移量,使九宫格在屏幕中央
 private int offset;
 // 九宫格的九个格子是否已经初始化
 private boolean init;
 // 格子的半径
 private int radius;
 // 密码
 private String password = "123456";
 // 九个格子
 private Point[][] points = new Point[3][3];
 private int width;
 private int height;
 private Matrix matrix = new Matrix();
 private float moveX = -1;
 private float moveY = -1;
 // 是否手指在移动
 private boolean isMove;
 // 是否可以触摸,当用户抬起手指,划出九宫格的密码不正确时为不可触摸
 private boolean isTouch = true;
 // 用来存储记录被按下的点
 private List<Point> pressedPoint = new ArrayList<>();
 // 屏幕解锁监听器
 private OnScreenLockListener listener;

 public ScreenLockView(Context context) {
  this(context, null);
 }

 public ScreenLockView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public ScreenLockView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  errorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_error);
  normalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_normal);
  pressedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_pressed);
  lineErrorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.line_error);
  linePressedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.line_pressed);
  radius = normalBitmap.getWidth() / 2;
 }
 ...
}

在构造器中我们主要就是把图片加载完成,并且得到了格子的半径,即图片宽度的一半。

之后我们来看看 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 if (widthSize > heightSize) {
  offset = (widthSize - heightSize) / 2;
 } else {
  offset = (heightSize - widthSize) / 2;
 }
 setMeasuredDimension(widthSize, heightSize);
}

onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中,主要得到对应的偏移量,以便在下面的 onDraw(Canvas canvas) 把九宫格绘制在屏幕中央。

下面就是 onDraw(Canvas canvas) 方法:

@Override
protected void onDraw(Canvas canvas) {
 if (!init) {
  width = getWidth();
  height = getHeight();
  initPoint();
  init = true;
 }
 // 画九宫格的格子
 drawPoint(canvas);

 if (moveX != -1 && moveY != -1) {
  // 画直线
  drawLine(canvas);
 }
}

首先判断了是否为第一次调用 onDraw(Canvas canvas) 方法,若为第一次则对 points 进行初始化:

// 初始化点
private void initPoint() {
 points[0][0] = new Point(width / 4, offset + width / 4, "0");
 points[0][1] = new Point(width / 2, offset + width / 4, "1");
 points[0][2] = new Point(width * 3 / 4, offset + width / 4, "2");

 points[1][0] = new Point(width / 4, offset + width / 2, "3");
 points[1][1] = new Point(width / 2, offset + width / 2, "4");
 points[1][2] = new Point(width * 3 / 4, offset + width / 2, "5");

 points[2][0] = new Point(width / 4, offset + width * 3 / 4, "6");
 points[2][1] = new Point(width / 2, offset + width * 3 / 4, "7");
 points[2][2] = new Point(width * 3 / 4, offset + width * 3 / 4, "8");
}

initPoint() 方法中主要创建了九个格子,并设置了相应的位置和密码。初始化完成之后把 init 置为 false ,下次不会再调用。

回过头再看看 onDraw(Canvas canvas) 中其他的逻辑,接下来调用了 drawPoint(canvas) 来绘制格子:

// 画九宫格的格子
private void drawPoint(Canvas canvas) {
 for (int i = 0; i < points.length; i++) {
  for (int j = 0; j < points[i].length; j++) {
   int state = points[i][j].getState();
   if (state == Point.NORMAL_MODE) {
    canvas.drawBitmap(normalBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
   } else if (state == Point.PRESSED_MODE) {
    canvas.drawBitmap(pressedBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
   } else {
    canvas.drawBitmap(errorBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
   }
  }
 }
}

在绘制格子还是很简单的,主要分为了三种:普通模式下的格子按下模式下的格子以及错误模式下的格子

onTouchEvent

在绘制好了格子之后,我们先不看最后的 drawLine(canvas) 方法,因为绘制直线是和用户手指的触摸事件息息相关的,所以我们先把目光转向 onTouchEvent(MotionEvent event) 方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
 if (isTouch) {
  float x = event.getX();
  float y = event.getY();
  Point point;
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
   // 判断用户触摸的点是否在九宫格的任意一个格子之内
    point = isPoint(x, y);
    if (point != null) {
     point.setState(Point.PRESSED_MODE); // 切换为按下模式
     pressedPoint.add(point);
    }
    break;
   case MotionEvent.ACTION_MOVE:
    if (pressedPoint.size() > 0) {
     point = isPoint(x, y);
     if (point != null) {
      if (!crossPoint(point)) {
       point.setState(Point.PRESSED_MODE);
       pressedPoint.add(point);
      }
     }
     moveX = x;
     moveY = y;
     isMove = true;
    }
    break;
   case MotionEvent.ACTION_UP:
    isMove = false;
    String tempPwd = "";
    for (Point p : pressedPoint) {
     tempPwd += p.getMark();
    }
    if (listener != null) {
     listener.getStringPassword(tempPwd);
    }

    if (tempPwd.equals(password)) {
     if (listener != null) {
      listener.isPassword(true);
     }
    } else {
     for (Point p : pressedPoint) {
      p.setState(Point.ERROR_MODE);
     }
     isTouch = false;
     this.postDelayed(runnable, 1000);
     if (listener != null) {
      listener.isPassword(false);
     }
    }
    break;
  }
  invalidate();
 }
 return true;
}

public interface OnScreenLockListener {
 public void getStringPassword(String password);
 public void isPassword(boolean flag);
}

public void setOnScreenLockListener(OnScreenLockListener listener) {
 this.listener = listener;
}

MotionEvent.ACTION_DOWN 中,先在 isPoint(float x, float y) 方法内判断了用户触摸事件的坐标点是否在九宫格的任意一格之内。如果是,则需要把该九宫格的格子添加到 pressedPoint 中:

// 该触摸点是否为格子
private Point isPoint(float x, float y) {
 Point point;
 for (int i = 0; i < points.length; i++) {
  for (int j = 0; j < points[i].length; j++) {
   point = points[i][j];
   if (isContain(point, x, y)) {
    return point;
   }
  }
 }
 return null;
}

// 该点(x,y)是否被包含
private boolean isContain(Point point, float x, float y) {
 // 该点的(x,y)与格子圆心的距离若小于半径就是被包含了
 return Math.sqrt(Math.pow(x - point.getX(), 2f) + Math.pow(y - point.getY(), 2f)) <= radius;
}

接下来就是要看 MotionEvent.ACTION_MOVE 的逻辑了。一开始判断了用户触摸的点是否为九宫格的某个格子。但是比 MotionEvent.ACTION_DOWN 还多了一个步骤:若用户触摸了某个格子,还要判断该格子是否已经被包含在 pressedPoint 里面了。

// 是否该格子已经被包含在pressedPoint里面了
private boolean crossPoint(Point point) {
 if (pressedPoint.contains(point)) {
  return true;
 }
 return false;
}

最后来看看 MotionEvent.ACTION_UP ,把 pressedPoint 里保存的格子遍历后得到用户划出的密码,再和预先设置的密码比较,若相同则回调 OnScreenLockListener 监听器;不相同则把 pressedPoint 中的所有格子的模式设置为错误模式,并在 runnable 中调用 reset() 清空 pressedPoint ,重绘视图,再回调监听器。

private Runnable runnable = new Runnable() {
 @Override
 public void run() {
  isTouch = true;
  reset();
  invalidate();
 }
};

// 重置格子
private void reset(){
 for (int i = 0; i < points.length; i++) {
  for (int j = 0; j < points[i].length; j++) {
   points[i][j].setState(Point.NORMAL_MODE);
  }
 }
 pressedPoint.clear();
}

现在我们回过头来看看之前在 onDraw(Canvas canvas) 里面的 drawLine(Canvas canvas) 方法:

// 画直线
private void drawLine(Canvas canvas) {

 // 将pressedPoint中的所有格子依次遍历,互相连线
 for (int i = 0; i < pressedPoint.size() - 1; i++) {
  // 得到当前格子
  Point point = pressedPoint.get(i);
  // 得到下一个格子
  Point nextPoint = pressedPoint.get(i + 1);
  // 旋转画布
  canvas.rotate(RotateDegrees.getDegrees(point, nextPoint), point.getX(), point.getY());

  matrix.reset();
  // 根据距离设置拉伸的长度
  matrix.setScale(getDistance(point, nextPoint) / linePressedBitmap.getWidth(), 1f);
  // 进行平移
  matrix.postTranslate(point.getX(), point.getY() - linePressedBitmap.getWidth() / 2);

  if (point.getState() == Point.PRESSED_MODE) {
   canvas.drawBitmap(linePressedBitmap, matrix, null);
  } else {
   canvas.drawBitmap(lineErrorBitmap, matrix, null);
  }
  // 把画布旋转回来
  canvas.rotate(-RotateDegrees.getDegrees(point, nextPoint), point.getX(), point.getY());
 }

 // 如果是手指在移动的情况
 if (isMove) {
  Point lastPoint = pressedPoint.get(pressedPoint.size() - 1);
  canvas.rotate(RotateDegrees.getDegrees(lastPoint, moveX, moveY), lastPoint.getX(), lastPoint.getY());

  matrix.reset();
  Log.i(TAG, "the distance : " + getDistance(lastPoint, moveX, moveY) / linePressedBitmap.getWidth());
  matrix.setScale(getDistance(lastPoint, moveX, moveY) / linePressedBitmap.getWidth(), 1f);
  matrix.postTranslate(lastPoint.getX(), lastPoint.getY() - linePressedBitmap.getWidth() / 2);
  canvas.drawBitmap(linePressedBitmap, matrix, null);

  canvas.rotate(-RotateDegrees.getDegrees(lastPoint, moveX, moveY), lastPoint.getX(), lastPoint.getY());
 }
}

// 根据point和坐标点计算出之间的距离
private float getDistance(Point point, float moveX, float moveY) {
 Point b = new Point(moveX,moveY,null);
 return getDistance(point,b);
}

// 根据两个point计算出之间的距离
private float getDistance(Point point, Point nextPoint) {
 return (float) Math.sqrt(Math.pow(nextPoint.getX() - point.getX(), 2f) + Math.pow(nextPoint.getY() - point.getY(), 2f));
}

drawLine(Canvas canvas) 整体的逻辑并不复杂,首先将 pressedPoint 中的所有格子依次遍历,将它们连线。之后若是用户的手指还有滑动的话,把最后一个格子和用户手指触摸的点连线。

总结

ScreenLockView 中的代码差不多就是这些了,实现效果还算不错吧,当然你也可以自己设置喜欢的九宫格图片,只要替换一下就可以了。如果对本篇文章有疑问可以留言。希望本文的内容对大家开发Android能有所帮助。

(0)

相关推荐

  • Android 仿小米锁屏实现九宫格解锁功能(无需图片资源)

    最近公司要求做个九宫格解锁,本人用的是小米手机,看着他那个设置锁屏九宫格很好看,就做了该组件,不使用图片资源,纯代码实现. 尊重每个辛苦的博主,在http://blog.csdn.net/mu399/article/details/38734449的基础上进行修改 效果图: 关键代码类: MathUtil.Java /** * @author SoBan * @create 2016/12/5 15:52. */ public class MathUtil { public static dou

  • android 九宫格滑动解锁开机实例源码学习

    效果图由于网站占时不能上传,以后补上. NinePointLineView.java 复制代码 代码如下: package org.demo.custon_view; import org.demo.utils.MLog; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; imp

  • Android自定义控件实现九宫格解锁功能

    最终Android九宫格解锁效果如下 1.进行定义实体point点 public class Point { private float x; private float y; //正常模式 public static final int NORMAL_MODE = 1; //按下模式 public static final int PRESSED_MODE = 2; //错误模式 public static final int ERROR_MODE = 3; private int state

  • 使用Android自定义控件实现滑动解锁九宫格

    本文概述:  滑动解锁九宫格的分析: 1.需要自定义控件: 2.需要重写事件onTouchEvent(); 3.需要给九个点设置序号和坐标,这里用Map类就行: 4.需要判断是否到滑到过九点之一,并存储滑到过的点的序号,而且需要一个方法可以返回它们,这里用List类就行: 滑动解锁当前还是比较流行的,今天写了个简单的滑动解锁九宫格的例程,分享出来让初学者看看. 我的是这样的: Demo 首先,自定义一个View /** * 九宫格 */ public class NineGridView ext

  • 轻松实现Android自定义九宫格图案解锁

    Android实现九宫格图案解锁,自带将图案转化成数字密码的功能,代码如下: LockPatternView.java package com.jackie.lockpattern; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.text.TextUtils; i

  • Android实现九宫格解锁的方法

    相信大家都有使用九宫格解锁,比如在设置手机安全项目中,可以使用九宫格解锁,提高安全性,以及在使用支付功能的时候,为了提高安全使用九宫锁,今天就为大家介绍Android实现九宫格的方法,分享给大家供大家参考.具体如下: 运行效果截图如下: 具体代码如下: 布局文件如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas

  • Android实现九宫格解锁的实例代码

    当年感觉九宫格解锁很是高大上,一脸懵逼,今天正好要做解锁这一块业务,回头来看九宫格,这特么简单啊 首先理清一下逻辑,我们要做NxN的九宫格 下图是3x3的简单图例 // -(--)-(--)-(--)- // -(--)-(--)-(--)- // -(--)-(--)-(--)- 我们就把九宫格分解成 外圆 .内圆.连线三部分 外圆半径Radius,内圆半径dp(5) 建立一个集合来放置 外圆的圆心( 内圆的圆心也一样) private ArrayList<Point> mListCircl

  • 轻松实现安卓(Android)九宫格解锁

    效果图 思路 首先我们来分析一下实现九宫格解锁的思路:当用户的手指触摸到某一个点时,先判断该点是否在九宫格的某一格范围之内,若在范围内,则该格变成选中的状态:之后用户手指滑动的时候,以该格的圆心为中心,用户手指为终点,两点连线.最后当用户手指抬起时,判断划过的九宫格密码是否和原先的密码匹配. 大致的思路流程就是上面这样的了,下面我们可以来实践一下. Point 类 我们先来创建一个 Point 类,用来表示九宫格锁的九个格子.除了坐标 x ,y 之外,还有三种模式:正常模式.按下模式和错误模式.

  • Android实现九宫格解锁

    相信大家都有使用九宫格解锁,比如在设置手机安全项目中,可以使用九宫格解锁,提高安全性,以及在使用支付功能的时候,为了提高安全使用九宫锁,今天就为大家介绍Android实现九宫格的方法,分享给大家供大家参考.具体如下: 运行效果截图如下: 具体代码如下: 布局文件如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas

  • Android自定义控件实现九宫格解锁

    关于九宫格解锁,我看了不少博客,但是都感觉很复杂,可能我的功夫还不到,所以很多东西我不了解,但是我还是打算写一个自己的九宫格.我相信我的九宫格大家都能很快的理解,当然如果需要实现更复杂的功能,需要大家自己接着往深了挖掘. 代码文件​​​​​​ NineGroupView:为九宫格空间组 ToggleView:九宫格中的子View,也就是我们看到的圆形按钮,我自己定义的ToggleView可能不好看,当然大家可以自己定义更加好看的ToggleView. MarkBean:记录ToggleView的

  • 基于Python的Android图形解锁程序详解

    安卓手机的图形锁是3x3的点阵,按次序连接数个点从而达到锁定/解锁的功能.最少需要连接4个点,最多能连接9个点.网上也有暴力删除手机图形锁的方法,即直接干掉图形锁功能.但假如你想进入别人的手机,但又不想引起其警觉的话--你可以参考一下本文(前提条件:手机需要root,而且打开调试模式.一般来讲,如果用过诸如"豌豆荚手机助手"."360手机助手"一类的软件,都会被要求打开调试模式的.如果要删除手机内置软件,则需要将手机root). 首先科普一下,安卓手机是如何标记这9

  • Appium自动化测试实现九宫格解锁

    目录 背景 高级手势 轻敲(tap) 短按 (press) 长按 (long_press) 等待 (wait) 移动到 (moveTo) 释放 (release) 执行 (perform) 九宫格解锁 背景 我们在操作APP密码解锁里面就有一个九宫格滑动解锁,这时候我们就需要手动连续滑动进行操作,前面提到的swipe.scroll.drag_and_drop 这些就无法实现解锁操作,我们就需要学会使用高级手势操作. 高级手势 高级手势TouchAction可以实现一些针对手势的操作,比如滑动.长

  • 总结安卓(Android)中常用的跳转工具

    话不多说了,直接上代码,这篇文章包含了一些基本的并且常用的跳转工具,一起来看看吧. 首先,这是需要的对应的权限. <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="

随机推荐