首页 > 代码库 > 教你如何实现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上的九点连线锁