教你如何实现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 @Override 106 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 + selectedBitmapRadius 212 - 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 paint 233 */ 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 paint 244 */ 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 paint 256 */ 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 canvas 269 */ 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 canvas 293 * @param point 294 */ 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 canvas 309 * @param startX 310 * @param startY 311 * @param stopX 312 * @param stopY 313 */ 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 zkwlx 324 * 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 x 415 * @param y 416 * @return417 */ 418 public boolean isInMyPlace(int x, int y) { 419 boolean inX = x > seletedX 420 && x < (seletedX + selectedBitmapDiameter); 421 boolean inY = y > seletedY 422 && y < (seletedY + selectedBitmapDiameter); 423 424 return (inX && inY); 425 } 426 427 } 428 429 }
其实这个实现有个不完善的地方,就是表示用户整个滑动的顺序,我看一般的android手机上都是用小箭头代表,那个我想了好久也没想出来怎么实现,就自己发明了个方法,就是图中那种一层一层覆盖的方法,不过效果没有那个箭头好,嘿嘿。
我写这个View时特意让他独立些,要用的时候就跟普通View一样。
如果有什么不明白的,请直接留言,我会很快回复的,当然也可以给我发邮件:D
下面是整个程序的源码:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。