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

 更新时间:2021年09月10日 10:10:40   作者:一生中所爱  
这篇文章主要为大家详细介绍了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 = NORMAL_MODE;
 private String mark;

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

 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;
 }

 public int getState() {
  return state;
 }

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

 public String getMark() {
  return mark;
 }

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

2.自定义ScreenLockView

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) {
  super(context);
  init();
 }


 public ScreenLockView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public ScreenLockView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 private void init() {
  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;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, 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);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  if (!init) {
   width = getWidth();
   height = getHeight();
   initPoint();
   init = true;
  }
  drawPoint(canvas);
  if (moveX != -1 && moveY != -1) {
   drawLine(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));
 }
 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);
    }
   }
  }
 }

 //初始化九宫格的点
 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");

 }

 @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);
       this.postDelayed(runnable, 1000);
      }

     } 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;
 }

 private boolean crossPoint(Point point) {
  if (pressedPoint.contains(point)) {
   return true;
  }
  return false;
 }

 public interface OnScreenLockListener {
  public void getStringPassword(String password);

  public void isPassword(boolean flag);
 }

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

 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;
 }

 private boolean isContain(Point point, float x, float y) {
  return Math.sqrt(Math.pow(x - point.getX(), 2f) + Math.pow(y - point.getY(), 2f)) <= radius;
 }
 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();
 }
}

3.RotateDegress类

public class RotateDegrees {
 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;
 }

 public static float getDegrees(Point a, float bX,float bY){
  Point b = new Point(bX,bY,null);
  return getDegrees(a,b);
 }
}

用到的图片资源

4.MainActivity中使用

public class MainActivity extends AppCompatActivity {

 private ScreenLockView screenLockView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  screenLockView = (ScreenLockView) findViewById(R.id.slv);
  screenLockView.setOnScreenLockListener(new ScreenLockView.OnScreenLockListener() {
   @Override
   public void getStringPassword(String password) {

   }

   @Override
   public void isPassword(boolean flag) {
    String content;
    if (flag) {
     content = "密码正确";

    } else {
     content = "密码错误";
    }
    Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show();

   }
  });
 }
}

5.布局文件

<?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:id="@+id/activity_main"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 tools:context="com.example.admin.ninegridunlock.MainActivity">

 <com.example.admin.ninegridunlock.ScreenLockView
  android:id="@+id/slv"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />
</RelativeLayout>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • android中多线程下载实例

    android中多线程下载实例

    本文以实例的方式为大家介绍下android中的多线程下载附源码,感兴趣的朋友可以学习下哈
    2013-06-06
  • 关于Android中Gradle和jar包下载慢的问题及解决方法

    关于Android中Gradle和jar包下载慢的问题及解决方法

    这篇文章主要介绍了解决Android中Gradle和jar包下载慢的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Android提高之XML解析与生成实例详解

    Android提高之XML解析与生成实例详解

    这篇文章主要介绍了Android的XML解析与生成方法,相信对大家的Android项目开发有一定的借鉴参考作用,需要的朋友可以参考下
    2014-08-08
  • 很实用的Android日期计算类

    很实用的Android日期计算类

    这篇文章主要为大家详细介绍了很实用的Android日期计算类,一个是获取与今天时间差,另一个是日期格式化工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-02-02
  • Android网络编程之获取网络上的Json数据实例

    Android网络编程之获取网络上的Json数据实例

    这篇文章主要介绍了Android网络编程之获取网络上的Json数据实例,本文用完整的代码实例讲解了在Android中读取网络中Json数据的方法,需要的朋友可以参考下
    2014-10-10
  • Android实现购物车添加商品特效

    Android实现购物车添加商品特效

    这篇文章主要为大家详细介绍了Android实现购物车添加商品特效,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • Android调用手机摄像头拍照和录音功能

    Android调用手机摄像头拍照和录音功能

    这篇文章主要为大家详细介绍了Android调用手机摄像头拍照和录音功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Android仿微信网络加载弹出框

    Android仿微信网络加载弹出框

    这篇文章主要为大家详细介绍了Android仿微信网络加载弹出框,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • android自定义手表效果

    android自定义手表效果

    这篇文章主要为大家详细介绍了android自定义手表效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • 完美解决虚拟按键遮盖底部视图的问题

    完美解决虚拟按键遮盖底部视图的问题

    下面小编就为大家分享一篇完美解决虚拟按键遮盖底部视图的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-01-01

最新评论