超实用的Android手势锁制作实例教程
今天偶遇以github上gesturelock关于手势锁的一个例子(有兴趣的去搜索下看看),于是下载下来研究,无奈基本没有注释,代码上存在一些问题(当设置gravity=center_vertical无法进行手势选择,无意中发现的),于是借鉴这位仁兄的代码,自己重写写了一个,修复了一些问题,加入一些基本的自定义属性,在此先感谢这位兄弟~。
先上图,默认效果图:
当然可以自定义数量啊,颜色神马的,自定义效果图:
如果你有艺术细胞,可以给我推荐几个颜色,无奈个人审美有问题~
1、整体思路
a、自定义了一个relativelayout(gesturelockviewgroup)在里面会根据传入的每行的个数,生成多个gesturelockview(就是上面一个个小圈圈),然后会自动进行布局,里面的宽度,间距,内圆的直径,箭头的大小神马的都是百分比实现的,所以大胆的设置你喜欢的个数,只要你没有密集恐惧症~
b、gesturelockview有三个状态,没有手指触碰、手指触碰、和手指抬起,会根据这三个状态绘制不同的效果,以及抬起时的小箭头需要旋转的角度,会根据用户选择的gesturelockview,进行计算,在gesturelockviewgroup为每个gesturelockview设置
c、gesturelockviewgroup主要就是判断用户action_move,action_down , action_up时改变选中的gesturelockview的状态,并且记录下来,提供一定的回调。
下面开始看代码:
2、声明一些用户可以设置的属性:
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="color_no_finger_inner_circle" format="color" /> <attr name="color_no_finger_outer_circle" format="color" /> <attr name="color_finger_on" format="color" /> <attr name="color_finger_up" format="color" /> <attr name="count" format="integer" /> <attr name="trytimes" format="integer" /> <declare-styleable name="gesturelockviewgroup"> <attr name="color_no_finger_inner_circle" /> <attr name="color_no_finger_outer_circle" /> <attr name="color_finger_on" /> <attr name="color_finger_up" /> <attr name="count" /> <attr name="trytimes" /> </declare-styleable> </resources>
用户可以用过在xml文件中设置这些属性,改变外观,最多尝试次数以及数量等。
3、gesturelockview
package com.zhy.zhy_gesturelockview.view; import android.content.context; import android.graphics.canvas; import android.graphics.paint; import android.graphics.paint.style; import android.graphics.path; import android.view.view; public class gesturelockview extends view { private static final string tag = "gesturelockview"; /** * gesturelockview的三种状态 */ enum mode { status_no_finger, status_finger_on, status_finger_up; } /** * gesturelockview的当前状态 */ private mode mcurrentstatus = mode.status_no_finger; /** * 宽度 */ private int mwidth; /** * 高度 */ private int mheight; /** * 外圆半径 */ private int mradius; /** * 画笔的宽度 */ private int mstrokewidth = 2; /** * 圆心坐标 */ private int mcenterx; private int mcentery; private paint mpaint; /** * 箭头(小三角最长边的一半长度 = marrawrate * mwidth / 2 ) */ private float marrowrate = 0.333f; private int marrowdegree = -1; private path marrowpath; /** * 内圆的半径 = minnercircleradiusrate * mradus * */ private float minnercircleradiusrate = 0.3f; /** * 四个颜色,可由用户自定义,初始化时由gesturelockviewgroup传入 */ private int mcolornofingerinner; private int mcolornofingeroutter; private int mcolorfingeron; private int mcolorfingerup; public gesturelockview(context context , int colornofingerinner , int colornofingeroutter , int colorfingeron , int colorfingerup ) { super(context); this.mcolornofingerinner = colornofingerinner; this.mcolornofingeroutter = colornofingeroutter; this.mcolorfingeron = colorfingeron; this.mcolorfingerup = colorfingerup; mpaint = new paint(paint.anti_alias_flag); marrowpath = new path(); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); mwidth = measurespec.getsize(widthmeasurespec); mheight = measurespec.getsize(heightmeasurespec); // 取长和宽中的小值 mwidth = mwidth < mheight ? mwidth : mheight; mradius = mcenterx = mcentery = mwidth / 2; mradius -= mstrokewidth / 2; // 绘制三角形,初始时是个默认箭头朝上的一个等腰三角形,用户绘制结束后,根据由两个gesturelockview决定需要旋转多少度 float marrowlength = mwidth / 2 * marrowrate; marrowpath.moveto(mwidth / 2, mstrokewidth + 2); marrowpath.lineto(mwidth / 2 - marrowlength, mstrokewidth + 2 + marrowlength); marrowpath.lineto(mwidth / 2 + marrowlength, mstrokewidth + 2 + marrowlength); marrowpath.close(); marrowpath.setfilltype(path.filltype.winding); } @override protected void ondraw(canvas canvas) { switch (mcurrentstatus) { case status_finger_on: // 绘制外圆 mpaint.setstyle(style.stroke); mpaint.setcolor(mcolorfingeron); mpaint.setstrokewidth(2); canvas.drawcircle(mcenterx, mcentery, mradius, mpaint); // 绘制内圆 mpaint.setstyle(style.fill); canvas.drawcircle(mcenterx, mcentery, mradius * minnercircleradiusrate, mpaint); break; case status_finger_up: // 绘制外圆 mpaint.setcolor(mcolorfingerup); mpaint.setstyle(style.stroke); mpaint.setstrokewidth(2); canvas.drawcircle(mcenterx, mcentery, mradius, mpaint); // 绘制内圆 mpaint.setstyle(style.fill); canvas.drawcircle(mcenterx, mcentery, mradius * minnercircleradiusrate, mpaint); drawarrow(canvas); break; case status_no_finger: // 绘制外圆 mpaint.setstyle(style.fill); mpaint.setcolor(mcolornofingeroutter); canvas.drawcircle(mcenterx, mcentery, mradius, mpaint); // 绘制内圆 mpaint.setcolor(mcolornofingerinner); canvas.drawcircle(mcenterx, mcentery, mradius * minnercircleradiusrate, mpaint); break; } } /** * 绘制箭头 * @param canvas */ private void drawarrow(canvas canvas) { if (marrowdegree != -1) { mpaint.setstyle(paint.style.fill); canvas.save(); canvas.rotate(marrowdegree, mcenterx, mcentery); canvas.drawpath(marrowpath, mpaint); canvas.restore(); } } /** * 设置当前模式并重绘界面 * * @param mode */ public void setmode(mode mode) { this.mcurrentstatus = mode; invalidate(); } public void setarrowdegree(int degree) { this.marrowdegree = degree; } public int getarrowdegree() { return this.marrowdegree; } }
注释很详细,主要就是ondraw时,判断当前状态,绘制不同的显示效果;状态的改变都是gesturelockviewgroup的ontouchevent中设置的。
4、gesturelockviewgroup
package com.zhy.zhy_gesturelockview.view; import java.util.arraylist; import java.util.list; import android.content.context; import android.content.res.typedarray; import android.graphics.canvas; import android.graphics.color; import android.graphics.paint; import android.graphics.path; import android.graphics.point; import android.util.attributeset; import android.util.log; import android.view.motionevent; import android.view.view; import android.widget.relativelayout; import com.zhy.zhy_gesturelockview.r; import com.zhy.zhy_gesturelockview.view.gesturelockview.mode; /** * 整体包含n*n个gesturelockview,每个gesturelockview间间隔mmarginbetweenlockview, * 最外层的gesturelockview与容器存在mmarginbetweenlockview的外边距 * * 关于gesturelockview的边长(n*n): n * mgesturelockviewwidth + ( n + 1 ) * * mmarginbetweenlockview = mwidth ; 得:mgesturelockviewwidth = 4 * mwidth / ( 5 * * mcount + 1 ) 注:mmarginbetweenlockview = mgesturelockviewwidth * 0.25 ; * * @author zhy * */ public class gesturelockviewgroup extends relativelayout { private static final string tag = "gesturelockviewgroup"; /** * 保存所有的gesturelockview */ private gesturelockview[] mgesturelockviews; /** * 每个边上的gesturelockview的个数 */ private int mcount = 4; /** * 存储答案 */ private int[] manswer = { 0, 1, 2, 5, 8 }; /** * 保存用户选中的gesturelockview的id */ private list<integer> mchoose = new arraylist<integer>(); private paint mpaint; /** * 每个gesturelockview中间的间距 设置为:mgesturelockviewwidth * 25% */ private int mmarginbetweenlockview = 30; /** * gesturelockview的边长 4 * mwidth / ( 5 * mcount + 1 ) */ private int mgesturelockviewwidth; /** * gesturelockview无手指触摸的状态下内圆的颜色 */ private int mnofingerinnercirclecolor = 0xff939090; /** * gesturelockview无手指触摸的状态下外圆的颜色 */ private int mnofingeroutercirclecolor = 0xffe0dbdb; /** * gesturelockview手指触摸的状态下内圆和外圆的颜色 */ private int mfingeroncolor = 0xff378fc9; /** * gesturelockview手指抬起的状态下内圆和外圆的颜色 */ private int mfingerupcolor = 0xffff0000; /** * 宽度 */ private int mwidth; /** * 高度 */ private int mheight; private path mpath; /** * 指引线的开始位置x */ private int mlastpathx; /** * 指引线的开始位置y */ private int mlastpathy; /** * 指引下的结束位置 */ private point mtmptarget = new point(); /** * 最大尝试次数 */ private int mtrytimes = 4; /** * 回调接口 */ private ongesturelockviewlistener mongesturelockviewlistener; public gesturelockviewgroup(context context, attributeset attrs) { this(context, attrs, 0); } public gesturelockviewgroup(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); /** * 获得所有自定义的参数的值 */ typedarray a = context.gettheme().obtainstyledattributes(attrs, r.styleable.gesturelockviewgroup, defstyle, 0); int n = a.getindexcount(); for (int i = 0; i < n; i++) { int attr = a.getindex(i); switch (attr) { case r.styleable.gesturelockviewgroup_color_no_finger_inner_circle: mnofingerinnercirclecolor = a.getcolor(attr, mnofingerinnercirclecolor); break; case r.styleable.gesturelockviewgroup_color_no_finger_outer_circle: mnofingeroutercirclecolor = a.getcolor(attr, mnofingeroutercirclecolor); break; case r.styleable.gesturelockviewgroup_color_finger_on: mfingeroncolor = a.getcolor(attr, mfingeroncolor); break; case r.styleable.gesturelockviewgroup_color_finger_up: mfingerupcolor = a.getcolor(attr, mfingerupcolor); break; case r.styleable.gesturelockviewgroup_count: mcount = a.getint(attr, 3); break; case r.styleable.gesturelockviewgroup_trytimes: mtrytimes = a.getint(attr, 5); default: break; } } a.recycle(); // 初始化画笔 mpaint = new paint(paint.anti_alias_flag); mpaint.setstyle(paint.style.stroke); // mpaint.setstrokewidth(20); mpaint.setstrokecap(paint.cap.round); mpaint.setstrokejoin(paint.join.round); // mpaint.setcolor(color.parsecolor("#aaffffff")); mpath = new path(); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); mwidth = measurespec.getsize(widthmeasurespec); mheight = measurespec.getsize(heightmeasurespec); // log.e(tag, mwidth + ""); // log.e(tag, mheight + ""); mheight = mwidth = mwidth < mheight ? mwidth : mheight; // setmeasureddimension(mwidth, mheight); // 初始化mgesturelockviews if (mgesturelockviews == null) { mgesturelockviews = new gesturelockview[mcount * mcount]; // 计算每个gesturelockview的宽度 mgesturelockviewwidth = (int) (4 * mwidth * 1.0f / (5 * mcount + 1)); //计算每个gesturelockview的间距 mmarginbetweenlockview = (int) (mgesturelockviewwidth * 0.25); // 设置画笔的宽度为gesturelockview的内圆直径稍微小点(不喜欢的话,随便设) mpaint.setstrokewidth(mgesturelockviewwidth * 0.29f); for (int i = 0; i < mgesturelockviews.length; i++) { //初始化每个gesturelockview mgesturelockviews[i] = new gesturelockview(getcontext(), mnofingerinnercirclecolor, mnofingeroutercirclecolor, mfingeroncolor, mfingerupcolor); mgesturelockviews[i].setid(i + 1); //设置参数,主要是定位gesturelockview间的位置 relativelayout.layoutparams lockerparams = new relativelayout.layoutparams( mgesturelockviewwidth, mgesturelockviewwidth); // 不是每行的第一个,则设置位置为前一个的右边 if (i % mcount != 0) { lockerparams.addrule(relativelayout.right_of, mgesturelockviews[i - 1].getid()); } // 从第二行开始,设置为上一行同一位置view的下面 if (i > mcount - 1) { lockerparams.addrule(relativelayout.below, mgesturelockviews[i - mcount].getid()); } //设置右下左上的边距 int rightmargin = mmarginbetweenlockview; int bottommargin = mmarginbetweenlockview; int leftmagin = 0; int topmargin = 0; /** * 每个view都有右外边距和底外边距 第一行的有上外边距 第一列的有左外边距 */ if (i >= 0 && i < mcount)// 第一行 { topmargin = mmarginbetweenlockview; } if (i % mcount == 0)// 第一列 { leftmagin = mmarginbetweenlockview; } lockerparams.setmargins(leftmagin, topmargin, rightmargin, bottommargin); mgesturelockviews[i].setmode(mode.status_no_finger); addview(mgesturelockviews[i], lockerparams); } log.e(tag, "mwidth = " + mwidth + " , mgestureviewwidth = " + mgesturelockviewwidth + " , mmarginbetweenlockview = " + mmarginbetweenlockview); } } @override public boolean ontouchevent(motionevent event) { int action = event.getaction(); int x = (int) event.getx(); int y = (int) event.gety(); switch (action) { case motionevent.action_down: // 重置 reset(); break; case motionevent.action_move: mpaint.setcolor(mfingeroncolor); mpaint.setalpha(50); gesturelockview child = getchildidbypos(x, y); if (child != null) { int cid = child.getid(); if (!mchoose.contains(cid)) { mchoose.add(cid); child.setmode(mode.status_finger_on); if (mongesturelockviewlistener != null) mongesturelockviewlistener.onblockselected(cid); // 设置指引线的起点 mlastpathx = child.getleft() / 2 + child.getright() / 2; mlastpathy = child.gettop() / 2 + child.getbottom() / 2; if (mchoose.size() == 1)// 当前添加为第一个 { mpath.moveto(mlastpathx, mlastpathy); } else // 非第一个,将两者使用线连上 { mpath.lineto(mlastpathx, mlastpathy); } } } // 指引线的终点 mtmptarget.x = x; mtmptarget.y = y; break; case motionevent.action_up: mpaint.setcolor(mfingerupcolor); mpaint.setalpha(50); this.mtrytimes--; // 回调是否成功 if (mongesturelockviewlistener != null && mchoose.size() > 0) { mongesturelockviewlistener.ongestureevent(checkanswer()); if (this.mtrytimes == 0) { mongesturelockviewlistener.onunmatchedexceedboundary(); } } log.e(tag, "munmatchexceedboundary = " + mtrytimes); log.e(tag, "mchoose = " + mchoose); // 将终点设置位置为起点,即取消指引线 mtmptarget.x = mlastpathx; mtmptarget.y = mlastpathy; // 改变子元素的状态为up changeitemmode(); // 计算每个元素中箭头需要旋转的角度 for (int i = 0; i + 1 < mchoose.size(); i++) { int childid = mchoose.get(i); int nextchildid = mchoose.get(i + 1); gesturelockview startchild = (gesturelockview) findviewbyid(childid); gesturelockview nextchild = (gesturelockview) findviewbyid(nextchildid); int dx = nextchild.getleft() - startchild.getleft(); int dy = nextchild.gettop() - startchild.gettop(); // 计算角度 int angle = (int) math.todegrees(math.atan2(dy, dx)) + 90; startchild.setarrowdegree(angle); } break; } invalidate(); return true; } private void changeitemmode() { for (gesturelockview gesturelockview : mgesturelockviews) { if (mchoose.contains(gesturelockview.getid())) { gesturelockview.setmode(mode.status_finger_up); } } } /** * * 做一些必要的重置 */ private void reset() { mchoose.clear(); mpath.reset(); for (gesturelockview gesturelockview : mgesturelockviews) { gesturelockview.setmode(mode.status_no_finger); gesturelockview.setarrowdegree(-1); } } /** * 检查用户绘制的手势是否正确 * @return */ private boolean checkanswer() { if (manswer.length != mchoose.size()) return false; for (int i = 0; i < manswer.length; i++) { if (manswer[i] != mchoose.get(i)) return false; } return true; } /** * 检查当前左边是否在child中 * @param child * @param x * @param y * @return */ private boolean checkpositioninchild(view child, int x, int y) { //设置了内边距,即x,y必须落入下gesturelockview的内部中间的小区域中,可以通过调整padding使得x,y落入范围不变大,或者不设置padding int padding = (int) (mgesturelockviewwidth * 0.15); if (x >= child.getleft() + padding && x <= child.getright() - padding && y >= child.gettop() + padding && y <= child.getbottom() - padding) { return true; } return false; } /** * 通过x,y获得落入的gesturelockview * @param x * @param y * @return */ private gesturelockview getchildidbypos(int x, int y) { for (gesturelockview gesturelockview : mgesturelockviews) { if (checkpositioninchild(gesturelockview, x, y)) { return gesturelockview; } } return null; } /** * 设置回调接口 * * @param listener */ public void setongesturelockviewlistener(ongesturelockviewlistener listener) { this.mongesturelockviewlistener = listener; } /** * 对外公布设置答案的方法 * * @param answer */ public void setanswer(int[] answer) { this.manswer = answer; } /** * 设置最大实验次数 * * @param boundary */ public void setunmatchexceedboundary(int boundary) { this.mtrytimes = boundary; } @override public void dispatchdraw(canvas canvas) { super.dispatchdraw(canvas); //绘制gesturelockview间的连线 if (mpath != null) { canvas.drawpath(mpath, mpaint); } //绘制指引线 if (mchoose.size() > 0) { if (mlastpathx != 0 && mlastpathy != 0) canvas.drawline(mlastpathx, mlastpathy, mtmptarget.x, mtmptarget.y, mpaint); } } public interface ongesturelockviewlistener { /** * 单独选中元素的id * * @param position */ public void onblockselected(int cid); /** * 是否匹配 * * @param matched */ public void ongestureevent(boolean matched); /** * 超过尝试次数 */ public void onunmatchedexceedboundary(); } }
注释极其详细,用极其不过分~主要就是ontouchevent中对用户选择的gesturelockview进行判断,以及改变gesturelockview状态等。
5、布局文件
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_gesturelockview" android:layout_width="match_parent" android:layout_height="match_parent" > <com.zhy.zhy_gesturelockview.view.gesturelockviewgroup android:id="@+id/id_gesturelockviewgroup" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f2f2f7" android:gravity="center_vertical" zhy:count="3" zhy:trytimes="5" /> <!-- zhy:color_no_finger_inner_circle="#ff085d58" zhy:color_no_finger_outer_circle="#ff08f0e0" zhy:color_finger_on="#ff1734bf" --> </relativelayout>
有兴趣的可以自定义属性,把注释的代码添进去就行,当然你也可以什么都不设置,单纯设置宽度和高度,我觉得默认效果也是不错的 ~
6、调用
package com.zhy.zhy_gesturelockview; import android.app.activity; import android.os.bundle; import android.widget.toast; import com.zhy.zhy_gesturelockview.view.gesturelockviewgroup; import com.zhy.zhy_gesturelockview.view.gesturelockviewgroup.ongesturelockviewlistener; public class mainactivity extends activity { private gesturelockviewgroup mgesturelockviewgroup; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mgesturelockviewgroup = (gesturelockviewgroup) findviewbyid(r.id.id_gesturelockviewgroup); mgesturelockviewgroup.setanswer(new int[] { 1, 2, 3, 4,5 }); mgesturelockviewgroup .setongesturelockviewlistener(new ongesturelockviewlistener() { @override public void onunmatchedexceedboundary() { toast.maketext(mainactivity.this, "错误5次...", toast.length_short).show(); mgesturelockviewgroup.setunmatchexceedboundary(5); } @override public void ongestureevent(boolean matched) { toast.maketext(mainactivity.this, matched+"", toast.length_short).show(); } @override public void onblockselected(int cid) { } }); } }
上一篇: XHTML标准的版本
下一篇: java中设计模式(多例)的实例详解
推荐阅读