教你如何实现android上的九点连线锁
本文为木可文原创,转载请注明:转自木可文博客:http://www.cnblogs.com/coding-way/archive/2011/11/07/2238340.html
这两天研究了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) {// 如果已滑完,重置每个点的属性和lockString
111
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 // 一个点的ID
329 private int id;
330
331 // 当前点所指向的下一个点的ID,当没有时为自己ID
332 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 * @return
417 */
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
下面是整个程序的源码:
https://files.cnblogs.com/coding-way/MyCustomView.zip