首页 > 代码库 > 教你如何实现android上的九点连线锁

教你如何实现android上的九点连线锁

教你如何实现android上的九点连线锁 - 周柯文 - 博客园

这两天研究了View类,自己实现了一个九点连线锁,把心得分享下。

下面是实现截图:

 

我的思路是,首先绘制每个点,就是中间的小蓝点,当手指触摸到某个点的范围内时(就是当ACTION_DOWN发生在某个范围内时),绘制灰色大圆;当手指移动时(ACTION_MOVE),绘制每个点之间的线段,和最后一个点到手指当前位置的线段;当手指抬起时,把所有相关的坐标值设为初值0,并设置标志onUp为true,来等待用户下次画线。

我固定的给每个点设置了一个ID,如图:

 

 

然后设置了个全局的StringBuffer lockString ,每当用户滑动到某个点的范围内时,就向 lockString 的末尾添加这个点的ID,最终生成的String就可以保存在手机里,日后验证时就拿这个String验证。

当然里面还有很多细节,下面是整个NinePointLineView.java的源代码,通过注释应该就明白了(文章最后有整个程序的源码的下载链接):

 

  1 package org.demo.custon_view;  2   3 import org.demo.utils.MLog;  4   5 import android.content.Context;  6 import android.graphics.Bitmap;  7 import android.graphics.BitmapFactory;  8 import android.graphics.Canvas;  9 import android.graphics.Color; 10 import android.graphics.Paint; 11 import android.graphics.Paint.Cap; 12 import android.graphics.Typeface; 13 import android.util.AttributeSet; 14 import android.view.MotionEvent; 15 import android.view.View; 16  17 public class NinePointLineView extends View { 18  19     Paint linePaint = new Paint(); 20  21     Paint whiteLinePaint = new Paint(); 22  23     Paint textPaint = new Paint(); 24  25     // 由于两个图片都是正方形,所以获取一个长度就行了 26     Bitmap defaultBitmap = BitmapFactory.decodeResource(getResources(), 27             R.drawable.lock); 28     int defaultBitmapRadius = defaultBitmap.getWidth() / 2; 29  30     // 初始化被选中图片的直径、半径 31     Bitmap selectedBitmap = BitmapFactory.decodeResource(getResources(), 32             R.drawable.indicator_lock_area); 33     int selectedBitmapDiameter = selectedBitmap.getWidth(); 34     int selectedBitmapRadius = selectedBitmapDiameter / 2; 35  36     // 定义好9个点的数组 37     PointInfo[] points = new PointInfo[9]; 38  39     // 相应ACTION_DOWN的那个点 40     PointInfo startPoint = null; 41  42     // 屏幕的宽高 43     int width, height; 44  45     // 当ACTION_MOVE时获取的X,Y坐标 46     int moveX, moveY; 47  48     // 是否发生ACTION_UP 49     boolean isUp = false; 50  51     // 最终生成的用户锁序列 52     StringBuffer lockString = new StringBuffer(); 53  54     public NinePointLineView(Context context) { 55         super(context); 56         this.setBackgroundColor(Color.WHITE); 57         initPaint(); 58     } 59  60     public NinePointLineView(Context context, AttributeSet attrs) { 61         super(context, attrs); 62         this.setBackgroundColor(Color.WHITE); 63         initPaint(); 64     } 65  66     @Override 67     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 68         MLog.i("onMeasure"); 69         // 初始化屏幕大小 70         width = getWidth(); 71         height = getHeight(); 72         if (width != 0 && height != 0) { 73             initPoints(points); 74         } 75         MLog.i("width、height = " + width + "、" + height); 76         super.onMeasure(widthMeasureSpec, heightMeasureSpec); 77     } 78  79     @Override 80     protected void onLayout(boolean changed, int left, int top, int right, 81             int bottom) { 82         MLog.i("onLayout"); 83         super.onLayout(changed, left, top, right, bottom); 84     } 85  86     private int startX = 0, startY = 0; 87  88     @Override 89     protected void onDraw(Canvas canvas) { 90  91         canvas.drawText("用户的滑动顺序:" + lockString, 0, 40, textPaint); 92  93         if (moveX != 0 && moveY != 0 && startX != 0 && startY != 0) { 94             // 绘制当前活动的线段 95             drawLine(canvas, startX, startY, moveX, moveY); 96         } 97  98         drawNinePoint(canvas); 99 100         super.onDraw(canvas);101     }102 103     // 记住,这个DOWN和MOVE、UP是成对的,如果没从UP释放,就不会再获得DOWN;104 // 而获得DOWN时,一定要确认消费该事件,否则MOVE和UP不会被这个View的onTouchEvent接收105     @Override106     public boolean onTouchEvent(MotionEvent event) {107 108         boolean flag = true;109 110         if (isUp) {// 如果已滑完,重置每个点的属性和lockString111 112             finishDraw();113 114             // 当UP后,要返回false,把事件释放给系统,否则无法获得Down事件115             flag = false;116 117         } else {// 没滑完,则继续绘制118 119             handlingEvent(event);120 121             // 这里要返回true,代表该View消耗此事件,否则不会收到MOVE和UP事件122             flag = true;123 124         }125         return flag;126     }127 128     private void handlingEvent(MotionEvent event) {129         switch (event.getAction()) {130         case MotionEvent.ACTION_MOVE:131             moveX = (int) event.getX();132             moveY = (int) event.getY();133             MLog.i("onMove:" + moveX + "、" + moveY);134             for (PointInfo temp : points) {135                 if (temp.isInMyPlace(moveX, moveY) && temp.isNotSelected()) {136                     temp.setSelected(true);137                     startX = temp.getCenterX();138                     startY = temp.getCenterY();139                     int len = lockString.length();140                     if (len != 0) {141                         int preId = lockString.charAt(len - 1) - 48;142                         points[preId].setNextId(temp.getId());143                     }144                     lockString.append(temp.getId());145                     break;146                 }147             }148 149             invalidate(0, height - width, width, height);150             break;151 152         case MotionEvent.ACTION_DOWN:153             int downX = (int) event.getX();154             int downY = (int) event.getY();155             MLog.i("onDown:" + downX + "、" + downY);156             for (PointInfo temp : points) {157                 if (temp.isInMyPlace(downX, downY)) {158                     temp.setSelected(true);159                     startPoint = temp;160                     startX = temp.getCenterX();161                     startY = temp.getCenterY();162                     lockString.append(temp.getId());163                     break;164                 }165             }166             invalidate(0, height - width, width, height);167             break;168 169         case MotionEvent.ACTION_UP:170             MLog.i("onUp");171             startX = startY = moveX = moveY = 0;172             isUp = true;173             invalidate();174             break;175         default:176             MLog.i("收到其他事件!!");177             break;178         }179     }180 181     private void finishDraw() {182         for (PointInfo temp : points) {183             temp.setSelected(false);184             temp.setNextId(temp.getId());185         }186         lockString.delete(0, lockString.length());187         isUp = false;188         invalidate();189     }190 191     private void initPoints(PointInfo[] points) {192 193         int len = points.length;194 195         int seletedSpacing = (width - selectedBitmapDiameter * 3) / 4;196 197         // 被选择时显示图片的左上角坐标198         int seletedX = seletedSpacing;199         int seletedY = height - width + seletedSpacing;200 201         // 没被选时图片的左上角坐标202         int defaultX = seletedX + selectedBitmapRadius - defaultBitmapRadius;203         int defaultY = seletedY + selectedBitmapRadius - defaultBitmapRadius;204 205         // 绘制好每个点206         for (int i = 0; i < len; i++) {207             if (i == 3 || i == 6) {208                 seletedX = seletedSpacing;209                 seletedY += selectedBitmapDiameter + seletedSpacing;210 211                 defaultX = seletedX + selectedBitmapRadius212                         - defaultBitmapRadius;213                 defaultY += selectedBitmapDiameter + seletedSpacing;214 215             }216             points[i] = new PointInfo(i, defaultX, defaultY, seletedX, seletedY);217 218             seletedX += selectedBitmapDiameter + seletedSpacing;219             defaultX += selectedBitmapDiameter + seletedSpacing;220 221         }222     }223 224     private void initPaint() {225         initLinePaint(linePaint);226         initTextPaint(textPaint);227         initWhiteLinePaint(whiteLinePaint);228     }229 230     /**231      * 初始化文本画笔232      * @param paint233 */234     private void initTextPaint(Paint paint) {235         textPaint.setTextSize(30);236         textPaint.setAntiAlias(true);237         textPaint.setTypeface(Typeface.MONOSPACE);238     }239 240     /**241      * 初始化黑线画笔242      * 243      * @param paint244 */245     private void initLinePaint(Paint paint) {246         paint.setColor(Color.GRAY);247         paint.setStrokeWidth(defaultBitmap.getWidth());248         paint.setAntiAlias(true);249         paint.setStrokeCap(Cap.ROUND);250     }251 252     /**253      * 初始化白线画笔254      * 255      * @param paint256 */257     private void initWhiteLinePaint(Paint paint) {258         paint.setColor(Color.WHITE);259         paint.setStrokeWidth(defaultBitmap.getWidth() - 5);260         paint.setAntiAlias(true);261         paint.setStrokeCap(Cap.ROUND);262 263     }264 265     /**266      * 绘制已完成的部分267      * 268      * @param canvas269 */270     private void drawNinePoint(Canvas canvas) {271 272         if (startPoint != null) {273             drawEachLine(canvas, startPoint);274         }275 276         // 绘制每个点的图片277         for (PointInfo pointInfo : points) {278             if (pointInfo.isSelected()) {// 绘制大圈279                 canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),280                         pointInfo.getSeletedY(), null);281             }282             // 绘制点283             canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),284                     pointInfo.getDefaultY(), null);285         }286 287     }288 289     /**290      * 递归绘制每两个点之间的线段291      * 292      * @param canvas293      * @param point294 */295     private void drawEachLine(Canvas canvas, PointInfo point) {296         if (point.hasNextId()) {297             int n = point.getNextId();298             drawLine(canvas, point.getCenterX(), point.getCenterY(),299                     points[n].getCenterX(), points[n].getCenterY());300             // 递归301             drawEachLine(canvas, points[n]);302         }303     }304 305     /**306      * 先绘制黑线,再在上面绘制白线,达到黑边白线的效果307      * 308      * @param canvas309      * @param startX310      * @param startY311      * @param stopX312      * @param stopY313 */314     private void drawLine(Canvas canvas, float startX, float startY,315             float stopX, float stopY) {316         canvas.drawLine(startX, startY, stopX, stopY, linePaint);317         canvas.drawLine(startX, startY, stopX, stopY, whiteLinePaint);318     }319 320     /**321      * 用来表示一个点322      * 323      * @author zkwlx324      * 325 */326     private class PointInfo {327 328         // 一个点的ID329         private int id;330 331         // 当前点所指向的下一个点的ID,当没有时为自己ID332         private int nextId;333 334         // 是否被选中335         private boolean selected;336 337         // 默认时图片的左上角X坐标338         private int defaultX;339 340         // 默认时图片的左上角Y坐标341         private int defaultY;342 343         // 被选中时图片的左上角X坐标344         private int seletedX;345 346         // 被选中时图片的左上角Y坐标347         private int seletedY;348 349         public PointInfo(int id, int defaultX, int defaultY, int seletedX,350                 int seletedY) {351             this.id = id;352             this.nextId = id;353             this.defaultX = defaultX;354             this.defaultY = defaultY;355             this.seletedX = seletedX;356             this.seletedY = seletedY;357         }358 359         public boolean isSelected() {360             return selected;361         }362 363         public boolean isNotSelected() {364             return !isSelected();365         }366 367         public void setSelected(boolean selected) {368             this.selected = selected;369         }370 371         public int getId() {372             return id;373         }374 375         public int getDefaultX() {376             return defaultX;377         }378 379         public int getDefaultY() {380             return defaultY;381         }382 383         public int getSeletedX() {384             return seletedX;385         }386 387         public int getSeletedY() {388             return seletedY;389         }390 391         public int getCenterX() {392             return seletedX + selectedBitmapRadius;393         }394 395         public int getCenterY() {396             return seletedY + selectedBitmapRadius;397         }398 399         public boolean hasNextId() {400             return nextId != id;401         }402 403         public int getNextId() {404             return nextId;405         }406 407         public void setNextId(int nextId) {408             this.nextId = nextId;409         }410 411         /**412          * 坐标(x,y)是否在当前点的范围内413          * 414          * @param x415          * @param y416          * @return417 */418         public boolean isInMyPlace(int x, int y) {419             boolean inX = x > seletedX420                     && x < (seletedX + selectedBitmapDiameter);421             boolean inY = y > seletedY422                     && y < (seletedY + selectedBitmapDiameter);423 424             return (inX && inY);425         }426 427     }428 429 }

 

其实这个实现有个不完善的地方,就是表示用户整个滑动的顺序,我看一般的android手机上都是用小箭头代表,那个我想了好久也没想出来怎么实现,就自己发明了个方法,就是图中那种一层一层覆盖的方法,不过效果没有那个箭头好,嘿嘿。

 

我写这个View时特意让他独立些,要用的时候就跟普通View一样。

如果有什么不明白的,请直接留言,我会很快回复的,当然也可以给我发邮件:D

下面是整个程序的源码:

http://files.cnblogs.com/coding-way/MyCustomView.zip

教你如何实现android上的九点连线锁