Android仿手机QQ图案解锁功能
本文实例为大家分享了android仿手机qq图案解锁的具体代码,供大家参考,具体内容如下
ps:请不要再问我,为什么导入之后会乱码了。
其实,代码基本上都是从原生系统中提取的:lockpatternview、加密工具类,以及解锁逻辑等,我只是稍作修改,大家都知道,原生系统界面比较丑陋,因此,我特意把qq的apk解压了,从中拿了几张图案解锁的图片,一个简单的例子就这样诞生了。
好了,废话不多说,我们来看看效果(最后两张是最新4.4系统,炫一下,呵呵):
1.最关健的就是那个自定义九宫格view,代码来自framework下:lockpatternview,原生系统用的图片资源比较多,好像有7、8张吧,而且绘制的比较复杂,我找寻半天,眼睛都找瞎了,发现解压的qq里面就3张图片,一个圈圈,两个点,没办法,只能修改代码了,在修改的过程中,才发现,其实可以把原生的lockpatternview给简化,绘制更少的图片,达到更好的效果。总共优化有:①去掉了连线的箭头,②原生的连线只有白色一种,改成根据不同状态显示黄色和红色两张色,③.原生view是先画点再画线,使得线覆盖在点的上面,影响美观,改成先画连线再画点。
关健部分代码ondraw函数:
@override protected void ondraw(canvas canvas) { final arraylist<cell> pattern = mpattern; final int count = pattern.size(); final boolean[][] drawlookup = mpatterndrawlookup; if (mpatterndisplaymode == displaymode.animate) { // figure out which circles to draw // + 1 so we pause on complete pattern final int onecycle = (count + 1) * millis_per_circle_animating; final int spotincycle = (int) (systemclock.elapsedrealtime() - manimatingperiodstart) % onecycle; final int numcircles = spotincycle / millis_per_circle_animating; clearpatterndrawlookup(); for (int i = 0; i < numcircles; i++) { final cell cell = pattern.get(i); drawlookup[cell.getrow()][cell.getcolumn()] = true; } // figure out in progress portion of ghosting line final boolean needtoupdateinprogresspoint = numcircles > 0 && numcircles < count; if (needtoupdateinprogresspoint) { final float percentageofnextcircle = ((float) (spotincycle % millis_per_circle_animating)) / millis_per_circle_animating; final cell currentcell = pattern.get(numcircles - 1); final float centerx = getcenterxforcolumn(currentcell.column); final float centery = getcenteryforrow(currentcell.row); final cell nextcell = pattern.get(numcircles); final float dx = percentageofnextcircle * (getcenterxforcolumn(nextcell.column) - centerx); final float dy = percentageofnextcircle * (getcenteryforrow(nextcell.row) - centery); minprogressx = centerx + dx; minprogressy = centery + dy; } // todo: infinite loop here... invalidate(); } final float squarewidth = msquarewidth; final float squareheight = msquareheight; float radius = (squarewidth * mdiameterfactor * 0.5f); mpathpaint.setstrokewidth(radius); final path currentpath = mcurrentpath; currentpath.rewind(); // todo: the path should be created and cached every time we hit-detect // a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless the user is in progress, and // we are in stealth mode) final boolean drawpath = (!minstealthmode || mpatterndisplaymode == displaymode.wrong); // draw the arrows associated with the path (unless the user is in // progress, and // we are in stealth mode) boolean oldflag = (mpaint.getflags() & paint.filter_bitmap_flag) != 0; mpaint.setfilterbitmap(true); // draw with higher quality since we // render with transforms // draw the lines if (drawpath) { boolean anycircles = false; for (int i = 0; i < count; i++) { cell cell = pattern.get(i); // only draw the part of the pattern stored in // the lookup table (this is only different in the case // of animation). if (!drawlookup[cell.row][cell.column]) { break; } anycircles = true; float centerx = getcenterxforcolumn(cell.column); float centery = getcenteryforrow(cell.row); if (i == 0) { currentpath.moveto(centerx, centery); } else { currentpath.lineto(centerx, centery); } } // add last in progress section if ((mpatterninprogress || mpatterndisplaymode == displaymode.animate) && anycircles) { currentpath.lineto(minprogressx, minprogressy); } // chang the line color in different displaymode if (mpatterndisplaymode == displaymode.wrong) mpathpaint.setcolor(color.red); else mpathpaint.setcolor(color.yellow); canvas.drawpath(currentpath, mpathpaint); } // draw the circles final int paddingtop = getpaddingtop(); final int paddingleft = getpaddingleft(); for (int i = 0; i < 3; i++) { float topy = paddingtop + i * squareheight; // float centery = mpaddingtop + i * msquareheight + (msquareheight // / 2); for (int j = 0; j < 3; j++) { float leftx = paddingleft + j * squarewidth; drawcircle(canvas, (int) leftx, (int) topy, drawlookup[i][j]); } } mpaint.setfilterbitmap(oldflag); // restore default flag }
2.第二个值得学习的地方是(代码来自设置应用中):在创建解锁图案时的枚举使用,原生代码中使用了很多枚举,将绘制图案时的状态、底部两个按钮状态、顶部一个textview显示的提示文字都紧密的联系起来。因此,只用监听lockpatternview动态变化,对应改变底部button和顶部textview的状态即可实现联动,简单的方法可以实现很多代码才能实现的逻辑,个人很喜欢。
①全局的状态:
/** * keep track internally of where the user is in choosing a pattern. */ protected enum stage { // 初始状态 introduction(r.string.lockpattern_recording_intro_header, leftbuttonmode.cancel, rightbuttonmode.continuedisabled, id_empty_message, true), // 帮助状态 helpscreen(r.string.lockpattern_settings_help_how_to_record, leftbuttonmode.gone, rightbuttonmode.ok, id_empty_message, false), // 绘制过短 choicetooshort(r.string.lockpattern_recording_incorrect_too_short, leftbuttonmode.retry, rightbuttonmode.continuedisabled, id_empty_message, true), // 第一次绘制图案 firstchoicevalid(r.string.lockpattern_pattern_entered_header, leftbuttonmode.retry, rightbuttonmode.continue, id_empty_message, false), // 需要再次绘制确认 needtoconfirm(r.string.lockpattern_need_to_confirm, leftbuttonmode.cancel, rightbuttonmode.confirmdisabled, id_empty_message, true), // 确认出错 confirmwrong(r.string.lockpattern_need_to_unlock_wrong, leftbuttonmode.cancel, rightbuttonmode.confirmdisabled, id_empty_message, true), // 选择确认 choiceconfirmed(r.string.lockpattern_pattern_confirmed_header, leftbuttonmode.cancel, rightbuttonmode.confirm, id_empty_message, false); /** * @param headermessage * the message displayed at the top. * @param leftmode * the mode of the left button. * @param rightmode * the mode of the right button. * @param footermessage * the footer message. * @param patternenabled * whether the pattern widget is enabled. */ stage(int headermessage, leftbuttonmode leftmode, rightbuttonmode rightmode, int footermessage, boolean patternenabled) { this.headermessage = headermessage; this.leftmode = leftmode; this.rightmode = rightmode; this.footermessage = footermessage; this.patternenabled = patternenabled; } final int headermessage; final leftbuttonmode leftmode; final rightbuttonmode rightmode; final int footermessage; final boolean patternenabled; }
②.底部两个按钮的状态枚举:
/** * the states of the left footer button. */ enum leftbuttonmode { // 取消 cancel(android.r.string.cancel, true), // 取消时禁用 canceldisabled(android.r.string.cancel, false), // 重试 retry(r.string.lockpattern_retry_button_text, true), // 重试时禁用 retrydisabled(r.string.lockpattern_retry_button_text, false), // 消失 gone(id_empty_message, false); /** * @param text * the displayed text for this mode. * @param enabled * whether the button should be enabled. */ leftbuttonmode(int text, boolean enabled) { this.text = text; this.enabled = enabled; } final int text; final boolean enabled; } /** * the states of the right button. */ enum rightbuttonmode { // 继续 continue(r.string.lockpattern_continue_button_text, true), //继续时禁用 continuedisabled(r.string.lockpattern_continue_button_text, false), //确认 confirm(r.string.lockpattern_confirm_button_text, true), //确认是禁用 confirmdisabled(r.string.lockpattern_confirm_button_text, false), //ok ok(android.r.string.ok, true); /** * @param text * the displayed text for this mode. * @param enabled * whether the button should be enabled. */ rightbuttonmode(int text, boolean enabled) { this.text = text; this.enabled = enabled; } final int text; final boolean enabled; }
就这样,只要lockpatternview的状态一发生改变,就会动态改变底部两个button的文字和状态。很简洁,逻辑性很强。
3.第三个个人觉得比较有用的就是加密这一块了,为了以后方便使用,我把图案加密和字符加密分成两个工具类:lockpatternutils和lockpasswordutils两个文件,本文使用到的是lockpatternutils。其实所谓的图案加密也是将其通过sha-1加密转化成二进制数再保存到文件中(原生系统保存在/system/目录下,我这里没有权限,就保存到本应用目录下),解密时,也是将获取到用户的输入通过同样的方法加密,再与保存到文件中的对比,相同则密码正确,不同则密码错误。关健代码就是以下4个函数:
/** * serialize a pattern. 加密 * * @param pattern * the pattern. * @return the pattern in string form. */ public static string patterntostring(list<lockpatternview.cell> pattern) { if (pattern == null) { return ""; } final int patternsize = pattern.size(); byte[] res = new byte[patternsize]; for (int i = 0; i < patternsize; i++) { lockpatternview.cell cell = pattern.get(i); res[i] = (byte) (cell.getrow() * 3 + cell.getcolumn()); } return new string(res); } /** * save a lock pattern. * * @param pattern * the new pattern to save. * @param isfallback * specifies if this is a fallback to biometric weak */ public void savelockpattern(list<lockpatternview.cell> pattern) { // compute the hash final byte[] hash = lockpatternutils.patterntohash(pattern); try { // write the hash to file randomaccessfile raf = new randomaccessfile(slockpatternfilename, "rwd"); // truncate the file if pattern is null, to clear the lock if (pattern == null) { raf.setlength(0); } else { raf.write(hash, 0, hash.length); } raf.close(); } catch (filenotfoundexception fnfe) { // cant do much, unless we want to fail over to using the settings // provider log.e(tag, "unable to save lock pattern to " + slockpatternfilename); } catch (ioexception ioe) { // cant do much log.e(tag, "unable to save lock pattern to " + slockpatternfilename); } } /* * generate an sha-1 hash for the pattern. not the most secure, but it is at * least a second level of protection. first level is that the file is in a * location only readable by the system process. * * @param pattern the gesture pattern. * * @return the hash of the pattern in a byte array. */ private static byte[] patterntohash(list<lockpatternview.cell> pattern) { if (pattern == null) { return null; } final int patternsize = pattern.size(); byte[] res = new byte[patternsize]; for (int i = 0; i < patternsize; i++) { lockpatternview.cell cell = pattern.get(i); res[i] = (byte) (cell.getrow() * 3 + cell.getcolumn()); } try { messagedigest md = messagedigest.getinstance("sha-1"); byte[] hash = md.digest(res); return hash; } catch (nosuchalgorithmexception nsa) { return res; } } /** * check to see if a pattern matches the saved pattern. if no pattern * exists, always returns true. * * @param pattern * the pattern to check. * @return whether the pattern matches the stored one. */ public boolean checkpattern(list<lockpatternview.cell> pattern) { try { // read all the bytes from the file randomaccessfile raf = new randomaccessfile(slockpatternfilename, "r"); final byte[] stored = new byte[(int) raf.length()]; int got = raf.read(stored, 0, stored.length); raf.close(); if (got <= 0) { return true; } // compare the hash from the file with the entered pattern's hash return arrays.equals(stored, lockpatternutils.patterntohash(pattern)); } catch (filenotfoundexception fnfe) { return true; } catch (ioexception ioe) { return true; } }
好了,代码就分析到这里,非常感谢你看到了文章末尾。
本文源码(utf-8编码):android仿手机qq图案解锁
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。