最近看了慕课网一老师的视频,关于手势密码的研究,挺不错的,不过没上传源码,还有就是旋转角度的计算个人感觉不太好,于是整理出源代码如下:
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不过没进行手势加密,就咋不上传了。