教你如何实现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

下面是整个程序的源码:

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

时间: 2024-08-03 11:14:31

教你如何实现android上的九点连线锁的相关文章

教你如何选择Android游戏引擎

我们进行Android游戏开发时选择游戏引擎是必须的,但是该如何选择呢?哪个Android游戏引擎更加适合自己呢?本文就提供了三个游戏引擎的对比说明,阐述了它们各自的特点,为大家选择引擎提供了参照. 1.Ronkon 如果不是想帮助作者解决一大堆兼容性问题的话还是不要使用这个引擎,我在上面浪费了1天半,就只是为了把实例程序跑起来.开始还以为是我水平菜,结果一堆人没跑起来,和我一样都是黑屏.虽然它文档做得好但我还是放弃了,本来Android平台兼容性就是老大难,在来个半吊子的引擎我可没本事搞定.

Android 颜色渲染(九) PorterDuff及Xfermode详解

版权声明:本文为博主原创文章,未经博主允许不得转载. Android 颜色渲染(九)  PorterDuff及Xfermode详解 之前已经讲过了除ComposeShader之外Shader的全部子类, 在讲ComposeShader(组合渲染)之前,  由于构造ComposeShader需要 PorterDuffXfermode或者PorterDuff.Mode作为参数,所以在此先详细地了解下这两个类的作用,这对之后的绘图会有很大的帮 助: 在讲具体的使用之前补充一点知识,这就是 Proter

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

如何在android上 使用gif图片(android开源库android-gif-drawabl)

android开源库android-gif-drawable的使用 android的开源库是用来在android上显示gif图片的.我在网上查了一下,大家说这个框架写的不错,加载大的gif图片   不会内存溢出,于是我就想试试这个开源库,我下了作者的源代码和例子,但是我却跑不起来.不知道为什么,我又到网上去找使用这个开源库的例子发现有一个,我也下载了下来,发现还是跑不起来.我决定自己好好试试这个源代码,终于在我的努力下现在可以用了.废话完了 现在教大家怎么用这个库.大家不想看怎么做的 可以到后面

Unity3D之AssetBundle学习:Android上运行笔记

路径统一 在Android上加载StreamingAssets文件夹下的AssetBundle文件,首先需要对加载地址进行处理,注意PC.Android和IOS的地址不一致需要针对不同的平台不同的处理,通用代码如下: 1 //统一不同平台下 StreamingAssets 路径 2 public static readonly string STREAMING_ASSETS_PATH = 3 #if UNITY_ANDROID 4 "jar:file:///" + Applicatio

SharePanel – Android上简单的一键分享,可分享到微信QQ和新浪微博

SharePanel – Android上简单的一键分享,可分享到微信QQ和新浪微博 SharePanel Android上简单的一键分享可分享到微信QQ和新浪微博 简介 效果图 代码块 简介 最近在写一个小程序长微博工具,效果就是编辑长微博,然后一键分享到微信.QQ和新浪微博. 一开始是想直接用Intent.createChooser(target, title)来做,后来一想,这样做不是很好啊,会有许多乱七八糟的应用弹出来,我想优先分享到微信.QQ和微博,于是找了点资料,将一键分享这个部分做

ffmpeg在android上输出滑屏问题处理

ffmpeg部分机器上有花屏的问题 原代码如下: while(av_read_frame(formatCtx, &packet)>=0 && !_stop && NULL!=window && bInit) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode

Android上方便地开发的C程序

如果你基于没有一个专门的开发板练手,那你的Android手机也可以开发大多数C应用程序,安装好后编译C的编译器.本文只写一个Hello World的运行过程.优点是:不需要eclipse,不需要Android源码,不需要Android.mk,不需要NDK.一个C程序员就可以很好的利用Android了,需要一个编译器和一个adb要把程序放到Android系统中. 1.安装adb sudo apt-get install android-tools-adb 2.安装交叉工具链 sudo apt-ge

Android 上的 制表符(tab) —— 一个神奇的字符 (cocos2dx crash)

今天测试发现了游戏的一个问题,系统邮件,如果发了tab,在android上一打开邮件内容就会crash.而且他们很确定是tab的问题. 凭我多个月的经验(确实没多年...)来看,从来没听说在android上会因为一个tab崩溃,而且如果有这个问题,肯定会有很多人遇到,估计早就吵翻天了,搜索了一下,什么可用信息都没有. 于是写个测试工程测试了一下,分别在mac下和windows下,用文本编辑工具编辑了4个txt文档,utf有bom和无bom,内容是" tab abcd ",发现都能正常显