100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 自定义View之--九宫格图形密码锁

自定义View之--九宫格图形密码锁

时间:2018-07-21 23:42:33

相关推荐

自定义View之--九宫格图形密码锁

前言:

很多金融和几大商业银行的APP,都使用了九宫格图形密码锁来增强资金账户的安全。我也是金融公司的一员,在空余的时候,写下这个view,可以说是明智之举。

效果预览

这样一个逻辑差不多可以满足基本的需求了。接下来就看代码咯。

NineSquareView的成长

1、重写构造方法和初始化属性

private Paint pointPaint; //画点的画笔private Paint linePaint; // 画线的画笔private Path path;//路径private static int SQUAREWIDRH = 300; //默认正方形的边长private float mSquarewidth = SQUAREWIDRH; //每个正方形的边长 9个private float x, y; //手指在滑动的时候那个点的坐标private float startX, startY; //手指首次接触View的那个点的坐标private LinkedHashMap<String,Point> points = new LinkedHashMap<>(); //存放手指连接的点private OnFinishGestureListener finishGestureListener ; //当手指抬起时,触发的监听public NineSquareView(Context context) {this(context, null);}public NineSquareView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public NineSquareView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);linePaint = new Paint();linePaint.setStyle(Paint.Style.STROKE);linePaint.setColor(Color.CYAN);linePaint.setStrokeWidth(5);linePaint.setAntiAlias(true);linePaint.setStrokeCap(Paint.Cap.ROUND);pointPaint = new Paint();pointPaint.setStyle(Paint.Style.FILL);pointPaint.setColor(Color.parseColor("#cbd0de"));pointPaint.setStrokeWidth(40);pointPaint.setAntiAlias(true);pointPaint.setStrokeCap(Paint.Cap.ROUND);path =new Path();}public interface OnFinishGestureListener {void onfinish(LinkedHashMap<String,Point> points);}

2、重写onMeasure();

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int wideSize = MeasureSpec.getSize(widthMeasureSpec);int wideMode = MeasureSpec.getMode(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int width, height;if (wideMode == MeasureSpec.EXACTLY) { //精确值 或matchParentwidth = wideSize;} else {width = (int) (mSquarewidth * 3 + getPaddingLeft() + getPaddingRight());if (wideMode == MeasureSpec.AT_MOST) {width = Math.min(width, wideSize);}}if (heightMode == MeasureSpec.EXACTLY) { //精确值 或matchParentheight = heightSize;} else {height = (int) (mSquarewidth * 3 + getPaddingTop() + getPaddingBottom());if (heightMode == MeasureSpec.AT_MOST) {height = Math.min(height, heightSize);}}setMeasuredDimension(width, height);mSquarewidth = (int) (Math.min(width - getPaddingLeft() - getPaddingRight(),height - getPaddingTop() - getPaddingBottom()) * 1.0f / 3);}

mSquarewidth始终是View的三分之一的宽度。对OnMeasure()方法还不是很懂的。可以去看看鸿神写的博客Android 自定义View (二) 进阶。

3、重写onTouchEvent();

@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:startX = ev.getX();startY = ev.getY();break;case MotionEvent.ACTION_MOVE:x = ev.getX();y = ev.getY();invalidate();break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:x = 0;y = 0;startX = 0;startY = 0;finishGestureListener.onfinish(points);points.clear();invalidate();break;}return true;}

在手指离开屏幕的时候,就是绘制完成的时候,所有数据清零。并触发finishGestureListener,去处理当前用户连接的points.

4.重写onDraw();

最重要的,最精彩的部分来了。首先我们得把九个灰点画出来。来个双层for循环就搞定。

for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {pointPaint.setColor(Color.parseColor("#cbd0de"));canvas.drawPoint(mSquarewidth * (0.5f + i),mSquarewidth * (0.5f + j),pointPaint);}}

每个灰色的点都画在正方形的中央。可接下来有个问题就要思考了,我们的手指去绘制的时候,要判断手指触碰的点是不是正好是那些个灰点。判断两个坐标是否相等?NONONO,我们画的点比我们的手指要细些。手指要精确的触碰到那个灰点,估计有点困难。照这样下去,你的app早就被用户卸载了。

我们可以给一个范围,这个范围是用户触碰的点离最近的那个灰点的距离。比如mSquarewidth * 0.3f,如果手指触摸在这个范围内,就说明用户想要绘制这个点。这个范围不能超过mSquarewidth * 0.5f,然后,我们把这个点加入到集合中。

for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (Math.abs(startX - mSquarewidth * (0.5f + i)) < mSquarewidth * 0.3f &&Math.abs(startY - mSquarewidth * (0.5f + j)) < mSquarewidth * 0.3f) {path.moveTo(mSquarewidth * (0.5f + i), mSquarewidth * (0.5f + j));path.lineTo(x, y);canvas.drawPath(path,linePaint);path.reset();Point point =new Point(mSquarewidth * (0.5f + i),mSquarewidth * (0.5f + j));points.put(i+":"+j,point);System.out.println(points.size());System.out.println(i+"//"+j);}}}

这样写完后,运行写代码。结果就是,只能加入手指点下去的第一个点,想连接下一个点,怎么办?继续思考,写代码。刚才,我们已经连接到了第一个点,想要连接到第二个点,我们必须滑动我们的手指,滑动的时候,坐标变为了x,y.而且时时刻刻在变动。再来一次范围判断,是不是就可以连接到第二个点了?答案是正确的!

for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (Math.abs(x - mSquarewidth * (0.5f + i)) < mSquarewidth * 0.3f &&Math.abs(y - mSquarewidth * (0.5f + j)) <mSquarewidth * 0.3f) {Iterator<Point> iterator2 = collection.iterator();while(iterator2.hasNext()){Point point = iterator2.next();if(mSquarewidth * (0.5f + i)==point.getX() && mSquarewidth * (0.5f + j)==point.getY()){return;}}startX = mSquarewidth * (0.5f + i);startY = mSquarewidth * (0.5f + j);}}}

但要排除下,我们已经连接过的点。并把这连接好的第二个点设为起始点。这样就可以循环的连接点了。在一开始的效果预览中可以看到,连接过的点,会变一种颜色,而且还会有一个小圆环,点与点之间会有一根线连接着,不会消失。这也好办。

Collection<Point> collection = points.values();Iterator<Point> iterator = collection.iterator();if(iterator.hasNext()){Point point = iterator.next();drawCyanPoint(canvas,point);System.out.println("moveTo:"+point.getX()+"===="+point.getY());path.moveTo(point.getX(),point.getY());}while (iterator.hasNext()) {Point point = iterator.next();drawCyanPoint(canvas,point);System.out.println("lineTo:"+point.getX()+"===="+point.getY());path.lineTo(point.getX(),point.getY());}canvas.drawPath(path,linePaint);path.reset();

在画了灰点后,可以把map中的points连接起来。改变画笔的颜色,画上圆圈,这个圆圈的半径最好是你设置的那个范围的大小。我的是mSquarewidth * 0.3f。

//绘制手指划到的那个点,点外加上一层圈。public void drawCyanPoint(Canvas canvas, Point point){String s =getKey(point);String [] strings = s.split(":");int i= Integer.parseInt(strings[0]);int j=Integer.parseInt(strings[1]);pointPaint.setColor(Color.CYAN);canvas.drawPoint(mSquarewidth * (0.5f + i),mSquarewidth * (0.5f + j),pointPaint);canvas.drawCircle(mSquarewidth * (0.5f + i),mSquarewidth * (0.5f + j),mSquarewidth * 0.3f,linePaint);}//根据value取key值public String getKey(Point value){String key = "";Set<Map.Entry<String, Point>> set = points.entrySet();for(Map.Entry<String, Point> entry : set){if(entry.getValue().equals(value)){key = entry.getKey();break;}}return key;}

NinePointView的成长

这个View就是在绘制玩手势后的一个简单显示绘制的点的位置。

这个就比较简单了,很多都是 copy NineSquaredView的代码,就不细说了。

PswActivity的成长。

Activity中的就是逻辑和UI了。PswActivity包含设置密码锁和解锁并跳转到其他界面。大致逻辑我们都懂的,就不细说了。唯一要说的就是比较两次设置的密码是否一致,以及设置密码与解锁密码是否一致。我们要比较两次的密码是否一致,其实就是比较两次绘制时的绘制点的个数,位置是否一致。

public boolean isEquals(LinkedHashMap<String, Point> pointsOne,LinkedHashMap<String, Point> pointsTwo) {Iterator<String> iterator = pointsOne.keySet().iterator();Iterator<String> iterator2 = pointsTwo.keySet().iterator();if (pointsOne.size() != pointsTwo.size()) {return false;}while (iterator.hasNext()) {String s = iterator.next();String s2 = iterator2.next();if (!s.equals(s2)) {return false;}}return true;}

因为LinkedHashMap是有序的,所以才能这样一个一个对应的去比较。我们设置密码后,密码是需要存放在本地的,SharedPreferences来帮忙了。等到下一次打开APP的时候,才能与解锁密码作比较。可寻遍了SharedPreferences中的put相关方法,就是没有能把LinkedHashMap放进去的。刚还思考着呢,Stream来帮忙了。通过写流和读流,这样操作更加安全。

public String map2String(LinkedHashMap<String, Point> hashmap) {// 实例化一个ByteArrayOutputStream对象,用来装载压缩后的字节文件。ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();String sceneListString = null;// 然后将得到的字符数据装载到ObjectOutputStreamObjectOutputStream objectOutputStream = null;try {objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);// writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它objectOutputStream.writeObject(hashmap);// 最后,用Base64.encode将字节文件转换成Base64编码保存在String中sceneListString = new String(Base64.encode(byteArrayOutputStream.toByteArray(), Base64.DEFAULT),"utf8");// 关闭objectOutputStreamobjectOutputStream.close();} catch (IOException e) {e.printStackTrace();}return sceneListString;}public LinkedHashMap<String, Point> getHashMap() {String liststr = preferences.getString(PREFERENCENAME, null);try {return string2Map(liststr);} catch (StreamCorruptedException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}public LinkedHashMap<String, Point> string2Map(String SceneListString) throwsIOException, ClassNotFoundException {byte[] mobileBytes = Base64.decode(SceneListString.getBytes(),Base64.DEFAULT);ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(mobileBytes);ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);LinkedHashMap<String, Point> SceneList = (LinkedHashMap<String, Point>) objectInputStream.readObject();objectInputStream.close();return SceneList;}

所有代码链接:

/Demidong/ClockView.git

That all,欢迎评论和交流!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。