最近看了慕课网一老师的视频,关于手势密码的研究,挺不错的,不过没上传源码,还有就是旋转角度的计算个人感觉不太好,于是整理出源代码如下:
import java.util.ArrayList;
import java.util.List;
import mg.lanyan.ui.R;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@SuppressLint({ "DrawAllocation", "ClickableViewAccessibility" })
public class LockPaternView extends View{
public boolean isOnTouch=false;
private int mScreenWidth;
private int mScreenHeight;
/**九宫格的点集合*/
private Point [][] pointArray=new Point[3][3];
/**避免每次都初始化点*/
private boolean isFirst;
/**X轴的偏移量*/
private float offsetX;
/**Y轴的偏移量*/
private float offsetY;
/**所需要的图片资源id*/
private int normal=R.drawable.nor,press=R.drawable.press,error=R.drawable.error,linePress=R.drawable.linepress,lineError=R.drawable.lineerror;
/**通过资源id得到的图片Bitmap*/
private Bitmap mBitmapNormal,mBitmapPress,mBitmapError,mBitmapLinePress,mBitmapLineError;
/**绘制图案画笔*/
private Paint mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);;
/**图案半径*/
private int mRadioR;
/**存储按下的点集合*/
private List<Point> pointList=new ArrayList<Point>();
private float mCurrx,mCurrY;
/**是否选择*/
private boolean isSelect;
/**是否继续绘制*/
private boolean isMovePoint;
/**是否结束*/
private boolean isFinished;
/**用于缩放测量的矩阵*/
private Matrix matrix=new Matrix();
private int STATUS_PASSWORD=0;
private int STATUS_PASSWORD_OK=0;
private int STATUS_PASSWORD_ERROR=1;
public LockPaternView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public LockPaternView(Context context, AttributeSet attrs) {
this(context, attrs,0);
// TODO Auto-generated constructor stub
}
public LockPaternView(Context context) {
this(context,null);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
isOnTouch=true;
isMovePoint=false;
isFinished=false;
int action=event.getAction();
mCurrx=event.getX();
mCurrY=event.getY();
Point mPointIntersection=null;
switch (action) {
case MotionEvent.ACTION_DOWN:
resetPointList();
mPointIntersection=checkPoint();
if(mPointIntersection!=null){
isSelect=true;
}
break;
case MotionEvent.ACTION_MOVE:
if(isSelect){
mPointIntersection=checkPoint();
if(mPointIntersection==null){
isMovePoint=true;
}
}
break;
case MotionEvent.ACTION_UP:
isFinished=true;
isSelect=false;
isOnTouch=false;
break;
default:
break;
}
//手势没有结束
if(!isFinished&&isSelect&&mPointIntersection!=null){
if(crossPoint(mPointIntersection)){
isMovePoint=true;
}else{
mPointIntersection.status=Point.STATU_PRESS;
pointList.add(mPointIntersection);
}
}
//手势结束
if(isFinished){
if(pointList.size()<=4&&pointList.size()>=2){
//绘制错误
errorPoint();
STATUS_PASSWORD=STATUS_PASSWORD_ERROR;
}else if(pointList.size()<=1){
resetPointList();//绘制不成立
}else{
STATUS_PASSWORD=STATUS_PASSWORD_OK;
}
}
postInvalidate();
if(isFinished&&listener!=null){
if(STATUS_PASSWORD==STATUS_PASSWORD_ERROR){
listener.onFail();
}else if(STATUS_PASSWORD==STATUS_PASSWORD_OK){
String mPassword=getPassword();
listener.onSucceed(mPassword);
}
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if(!isFirst){
initPoint();
}
pointToCanvas(canvas);
if(pointList.size()>0){
Point a=pointList.get(0);
for (int i = 0; i < pointList.size(); i++) {
Point b=pointList.get(i);
lineToCanvas(canvas, a, b);
a=b;
}
if(isMovePoint){
lineToCanvas(canvas, a, new Point(mCurrx,mCurrY));
}
}
}
/*************************************Method****************************************/
/**
* 求两点之间的夹角
* @param px1
* @param py1
* @param px2
* @param py2
* @return
*/
public static float getAngle(float px1, float py1, float px2, float py2) {
// 两点的x、y值
float x = px2 - px1;
float y = py2 - py1;
double hypotenuse = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
// 斜边长度
double cos = x / hypotenuse;
double radian = Math.acos(cos);
// 求出弧度
float angle = (float) (180 / (Math.PI / radian));
// 用弧度算出角度
if (y < 0) {
angle = 180 + (180 - angle);
} else if ((y == 0) && (x < 0)) {
angle = 180;
}else if(x==0&&y==0){
angle=0;
}
return angle;
}
/**
* 绘制线条
* @param canvas
* @param a
* @param b
*/
public void lineToCanvas(Canvas canvas,Point a,Point b){
float scaleX=(float) getDistance(a, b)/mBitmapLinePress.getWidth();
float mAngle=getAngle(a.x, a.y, b.x, b.y);
canvas.rotate(mAngle,a.x,a.y);
if(a.status==Point.STATU_PRESS){
matrix.setScale(scaleX, 1);
matrix.postTranslate(a.x-mBitmapLinePress.getWidth()/2, a.y-mBitmapLinePress.getHeight()/2);//偏移
canvas.drawBitmap(mBitmapLinePress, matrix, mPaint);
}else{
matrix.setScale(scaleX, 1);
matrix.postTranslate(a.x-mBitmapLineError.getWidth()/2, a.y-mBitmapLineError.getHeight()/2);//偏移
canvas.drawBitmap(mBitmapLineError, matrix, mPaint);
}
canvas.rotate(-mAngle,a.x,a.y);
}
/**
* 判断是否是交叉点
* @param point
* @return
*/
public boolean crossPoint(Point point){
if(pointList.contains(point)){
return true;
}else{
/*point.status=Point.STATU_PRESS;
pointList.add(point);*/
return false;
}
}
public void resetPointList(){
if(pointList.size()>0){
for (int i = 0; i < pointList.size(); i++) {
Point point=pointList.get(i);
point.status=Point.STATU_NORMAL;
}
}
pointList.clear();
}
public void errorPoint(){
for(Point point:pointList){
point.status=Point.STATU_ERROR;
}
}
/***
* 检查点是否和九宫格的点有交集
*/
private Point checkPoint(){
for (int i = 0; i < pointArray.length; i++) {
for (int j = 0; j < pointArray[i].length; j++) {
Point point=pointArray[i][j];
if(isIntersection(point, new Point(mCurrx,mCurrY), mRadioR)){
return point;
}
}
}
return null;
}
/**
* 初始化图案的点
*/
private void initPoint(){
mScreenWidth=getWidth();
mScreenHeight=getHeight();
//横屏
if(mScreenWidth>mScreenHeight){
offsetX=(mScreenWidth-mScreenHeight)/2;
//正方形屏幕锁
mScreenWidth=mScreenHeight;
}
//竖屏
else{
offsetY=(mScreenHeight-mScreenWidth)/2;
mScreenHeight=mScreenWidth;
}
mBitmapNormal=BitmapFactory.decodeResource(getResources(), normal);
mBitmapPress=BitmapFactory.decodeResource(getResources(), press);
mBitmapError=BitmapFactory.decodeResource(getResources(), error);
mBitmapLinePress=BitmapFactory.decodeResource(getResources(), linePress);
mBitmapLineError=BitmapFactory.decodeResource(getResources(), lineError);
pointArray[0][0]=new Point(offsetX+mScreenWidth/4,offsetY+mScreenWidth/4);
pointArray[0][1]=new Point(offsetX+mScreenWidth/2,offsetY+mScreenWidth/4);
pointArray[0][2]=new Point(offsetX+mScreenWidth-mScreenWidth/4,offsetY+mScreenWidth/4);
pointArray[1][0]=new Point(offsetX+mScreenWidth/4,offsetY+mScreenWidth/2);
pointArray[1][1]=new Point(offsetX+mScreenWidth/2,offsetY+mScreenWidth/2);
pointArray[1][2]=new Point(offsetX+mScreenWidth-mScreenWidth/4,offsetY+mScreenWidth/2);
pointArray[2][0]=new Point(offsetX+mScreenWidth/4,offsetY+mScreenWidth-mScreenWidth/4);
pointArray[2][1]=new Point(offsetX+mScreenWidth/2,offsetY+mScreenWidth-mScreenWidth/4);
pointArray[2][2]=new Point(offsetX+mScreenWidth-mScreenWidth/4,offsetY+mScreenWidth-mScreenWidth/4);
mRadioR=mBitmapNormal.getWidth()/2;
int index=1;
for(Point[] point:pointArray){
for(Point mp:point){
mp.index=index;
index++;
}
}
isFirst=true;
}
/**
* 把点集合绘制到画布上
*/
private void pointToCanvas(Canvas canvas){
for (int i = 0; i < pointArray.length; i++) {
for (int j = 0; j < pointArray[i].length; j++) {
Point mPoint=pointArray[i][j];
if(mPoint.status==Point.STATU_NORMAL){
canvas.drawBitmap(mBitmapNormal, mPoint.x-mRadioR, mPoint.y-mRadioR, mPaint);
}else if(mPoint.status==Point.STATU_PRESS){
canvas.drawBitmap(mBitmapPress, mPoint.x-mRadioR, mPoint.y-mRadioR, mPaint);
}else if(mPoint.status==Point.STATU_ERROR){
canvas.drawBitmap(mBitmapError, mPoint.x-mRadioR, mPoint.y-mRadioR, mPaint);
}
}
}
}
/***
* 获取手势密码
* @return
*/
private String getPassword() {
// TODO Auto-generated method stub
String mPassword="";
for (int i = 0; i < pointList.size(); i++) {
Point p=pointList.get(i);
mPassword+=String.valueOf(p.index);
/*for (int k = 0; k < pointArray.length; k++) {
for (int j = 0; j < pointArray[k].length; j++) {
Point mp=pointArray[k][j];
if(p==mp){
mPassword+=k+""+j;
}
}
}*/
}
return mPassword;
}
/**
* 图案锁的点
* @author Administrator
*
*/
public static class Point{
/**图案锁的三种状态:正常状态*/
public static int STATU_NORMAL=0;
/**图案锁的三种状态:按下状态*/
public static int STATU_PRESS=1;
/**图案锁的三种状态:错误状态*/
public static int STATU_ERROR=2;
/**图案的x.y的点坐标*/
public float x;
public float y;
public int index,status;
public Point(){
}
public Point(float x,float y){
this.x=x;
this.y=y;
}
}
/**
* 计算两点之间的距离
* @param a
* @param b
* @return
*/
public static double getDistance(Point a,Point b){
return Math.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/**
* 判断是否有交集
* @param a
* @param b
* @param r
* @return
*/
public static boolean isIntersection(Point a,Point b,float r){
return getDistance(a, b)<r;
}
public interface OnLockPatternListener{
void onFail();
void onSucceed(String password);
}
private OnLockPatternListener listener;
public void setListener(OnLockPatternListener listener) {
this.listener = listener;
}
}
上面是自定义控件,主要用法: 布局引入view,activity 或者Fragment给控件设置监听回调函数判断。
主要用到的方法如下:
Handler handler = new Handler();
public void updateLockPatern() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
if (!mLockPatern.isOnTouch) {
mLockPatern.resetPointList();
mLockPatern.postInvalidate();
}
}
}, 1000);
}
@Override
public void onFail() {
// TODO Auto-generated method stub
super.onFail();
mLockToast.setText("手势密码连接最少5个点");
}
@Override
public void onSucceed(String password) {
// TODO Auto-generated method stub
super.onSucceed(password);
if (BaseFragmentActivity.mLockPatern.getLock().equals(password)) {
Intent intent=new Intent(getActivity(),APIClass.mLockLogin);
startActivity(intent);
getActivity().finish();
} else {
mLockToast.setText("密码错误,请重新绘制");
mLockPatern.errorPoint();
mLockPatern.postInvalidate();
updateLockPatern();
}
}
该项目需要资源文件:
nor.png ,press.png,error.png,linepress.png,lineerror.png
App接入后要考虑手势密码的几种情况: A .启动应用,如果有手势密码需要输入手势密码
B.创建手势密码
C.修改手势密码
D.onResume 生命周期监听屏幕开关时间间隔判断弹出手势密码界面,.
个人觉得开发用BaseActivity 提供registerReceiver,LockPaternActivity extends FragmentActivity 嵌套四个Fragmnent,根据Intent传入参数选择Fragment,我写了demo不过没进行手势加密,就咋不上传了。