Android自定义锁屏实现----仿正点闹钟滑屏解锁
来源:互联网 发布:python零基础入门 编辑:程序博客网 时间:2024/06/08 18:50
本文原创,转载请注明出处:http://blog.csdn.net/qinjuning
前几周看了下解锁的框架,基本上算是弄了个脸熟。看着别人花哨的解锁界面,心里也很痒痒的。于是,画了一天时间,
捣鼓出了这个成果----仿正点闹钟解锁。基本功能实现了,但程序效率问题以及程序的几处Bug都没有完全解决,留待以后有机
会弄吧。
与正点闹钟对比如下:
(我们的解锁界面) (正点闹钟的解锁界面)
如何知晓正点闹钟的布局构成?
毫无疑问,当我们想模仿任何一个优秀界面设计时,我们的第一想法肯定是能不能看到该App Apk包对应的资源文件(一般
来说,编译成Apk时,src目录源文件是看不到的,res目录可以看到)。但是,为了安全性以及技术壁垒,公司都会进行加密处理。
于是,我们想通过直接看layout布局文件泡汤了。当然,如果反编译看到那就无敌了。我不懂反编译,我就不多谈了。
就我所知Apk下图片资源是不会加密的,于是我们可以查看这些图片以及文件名来猜测界面的可能组成情况,这对我们后面的分
析很有用处。
真正我想说的:
一直以来,作为程序员我们一直疏忽了一个利器:--- Hierarchy Viewer工具,可以直接观看布局文件的构成。对于该工具的
具体用法请参考该文章:
《Android 实用工具Hierarchy Viewer实战》
PS : 如果你还羡慕其他人的布局 , 你就out了。 - -
我们针对正点闹钟的如下界面,利用工具查看,我们重点关注下图蓝色区域,如下:
上图中的蓝色区域利用Hierarchy Viewer工具得出如下的布局文件组成:
怎么样,是不是很清楚了?大概知道怎么布局了吧。针对上面的布局,再逐一详解下,最后形成一个类似的layout布局文件。
首先贴下这几张资源图片吧,大家心里也有个印象。
(这三张图片采用了黑色背景,便于观看)
其中最顶层RelativeLayout采用了一个背景图片,即圆弧形的图片。
上图的编号解释如下 :
编号 1 :TextView控件,显示一个图片资源,可能采用了android:background或者android:drawableLeft类似属性显示图片。
编号 2 :TextView控件 ---- “向右滑动结束提醒”。so easy
编号 3 :ImageView控件 --- 为了显示动画效果,采用了一个<animation-list>对应的动画文件,作为该控件的背景图片,
从其图片资源文件看验证了这点,采用了三张不同效果的图片。 so easy
编号 4 : ImageView控件,显示一张图片。 so easy
呵呵,怎么样是不是看起来比较简单,该layout布局文件如下:
说明: 代码块中 <SliderRelativeLayout>节点实则为<RelativeLayout>节点 , 大家请注意下。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent" ><SliderRelativeLayout android:id="@+id/slider_layout" android:layout_width="fill_parent"android:layout_height="63dip" android:layout_gravity="center"android:layout_marginTop="200dip"android:background="@drawable/step2_tip_2"> <!-- Lock OK icon --><ImageView android:id="@+id/getup_finish_ico"android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dip" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="@drawable/slider_ico_alarm"></ImageView> <!-- Arrow Animation --> <ImageView android:id="@+id/getup_arrow"android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="20dip" android:layout_toLeftOf="@id/getup_finish_ico" android:layout_alignTop="@id/getup_finish_ico" android:background="@anim/slider_tip_anim"> </ImageView> <!-- hint_unlock --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/getup_arrow" android:layout_alignTop="@id/getup_finish_ico" android:text="@string/hint_unlock"></TextView> <!-- slider img --> <TextView android:id="@+id/slider_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="5dip" android:drawableTop="@drawable/getup_slider_ico_normal"></TextView></SliderRelativeLayout></LinearLayout>
棘手问题: 如何实现动态效果,即绘制一个更随手指移动的东东 ?
先说下我的疑惑:如果参照正点闹钟显示的资源文件,即完全采用系统提供的控件,然后为这些控件提供触摸事件监听去滑动
前面所说的编号为1的TextView控件----滑动可以调用scrollTo()或者scrollBy()方法,我试了几次但是都没有效果,原因是scrollTo()
和scrollBy()方法并不偏移背景图片,仅仅偏移某个View的内容视图(onDraw()方法里绘制的),具体答案可以参考我的如下博客:
《Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解》的draw()方法流程。
不知道针对此,大家有没有好的意见。
我的实现方式
我们知道,自定义View/ViewGroup可以唯所欲为。因此,为了实现这种拖拽效果,实现了一个继承于RelativeLayout的
自定义ViewGroup控件,并且重写了其中的onTouchEvent()方法去绘制一个跟随手指而动的Bitmap图片资源。当然,这只是
第一步,还有很多细节需要处理,主要是一些坐标处理以及图片随着时间返回的问题,但这块内容大家看看代码注释加以理解
吧,我是真的不好描述出来。见后文所贴代码。
下面的知识主要是如何以一个APK的形式去实现解锁界面。
知识点一 : 如何以APK形式去达到锁屏的目的以及 屏幕变暗以及屏幕点亮的广播。
android.intent.action.SCREEN_ON --- 屏幕变亮的广播
android.intent.action.SCREEN_OFF ---- 屏幕点暗的广播
请参考该博客:http://www.2cto.com/kf/201111/109815.html
知识点二:Activity里如何屏蔽Home键和Back键?
请参考该博客:http://www.cnblogs.com/domybest/archive/2011/06/13/2080036.html
知识点三:KeyguardManager简介
请参考该博客:http://hubingforever.blog.163.com/blog/static/171040579201191524550863/
具体原因可以去看看我上篇博客<
Android框架浅析之锁屏(Keyguard)机制原理
>的最后面所述。这些知识,大家多多理解咯。
关于禁止下拉状态栏问题,由于不能在源码下编译,因此无法达到禁止下拉状态栏。但我们的目的主要是学习正点闹钟的布局
以及一种对它的一种可能的实现方式。这就够了。
OK,下面贴出我的自定义RelativeLayout的代码。
为了帮助大家更好的理解代码,最后说明图片是如何回退的?
基本思路:我们根据当前Bitmap拖拽图片的所在位置,每隔几毫秒就让Bitmap拖拽图片回退一定距离,并且请求View会
重新绘制,直到该Bitmap拖拽图片返回初始地方,即可显示一个回退动画效果了。代码中是利用Handler来控制的。
自定义RelativeLayout代码:
public class SliderRelativeLayout extends RelativeLayout {private static String TAG = "SliderRelativeLayout";private TextView tv_slider_icon = null; // 初始控件,用来判断是否为拖动?private Bitmap dragBitmap = null; //拖拽图片private Context mContext = null; // 初始化图片拖拽时的Bitmap对象private Handler mainHandler = null; //与主Activity通信的Handler对象private int mLastMoveX = 1000; //当前bitmap应该绘制的地方 , 初始值为足够大,可以认为看不见public SliderRelativeLayout(Context context) {super(context);mContext = context;initDragBitmap();}// 初始化图片拖拽时的Bitmap对象private void initDragBitmap() {if (dragBitmap == null)dragBitmap = BitmapFactory.decodeResource(mContext.getResources(),R.drawable.getup_slider_ico_pressed);}@Overrideprotected void onFinishInflate() {// TODO Auto-generated method stubsuper.onFinishInflate();// 该控件主要判断是否处于滑动点击区域。滑动时 处于INVISIBLE状态(消失),正常时处于VISIBLE(可见)状态tv_slider_icon = (TextView) findViewById(R.id.slider_icon);}public boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();Log.i(TAG, "onTouchEvent" + " X is " + x + " Y is " + y);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastMoveX = (int) event.getX();//处理Action_Down事件: 判断是否点击了滑动区域return handleActionDownEvenet(event);case MotionEvent.ACTION_MOVE:mLastMoveX = x; //保存了X轴方向 invalidate(); //重新绘制 return true;case MotionEvent.ACTION_UP://处理Action_Up事件: 判断是否解锁成功,成功则结束我们的Activity ;否则 ,缓慢回退该图片。handleActionUpEvent(event);return true;}return super.onTouchEvent(event);}// 绘制拖动时的图片public void onDraw(Canvas canvas) {super.onDraw(canvas);//Log.(TAG, "onDraw ######" );// 图片更随手势移动invalidateDragImg(canvas);}// 图片更随手势移动private void invalidateDragImg(Canvas canvas) {//Log.e(TAG, "handleActionUpEvenet : invalidateDragImg" );//以合适的坐标值绘制该图片int drawXCor = mLastMoveX - dragBitmap.getWidth();int drawYCor = tv_slider_icon.getTop();Log.i(TAG, "invalidateDragImg" + " drawXCor "+ drawXCor + " and drawYCor" + drawYCor); canvas.drawBitmap(dragBitmap, drawXCor < 0 ? 5 : drawXCor , drawYCor , null);}// 手势落下是,是否点中了图片,即是否需要开始移动private boolean handleActionDownEvenet(MotionEvent event) {Rect rect = new Rect();tv_slider_icon.getHitRect(rect);boolean isHit = rect.contains((int) event.getX(), (int) event.getY());if(isHit) //开始拖拽 ,隐藏该图片tv_slider_icon.setVisibility(View.INVISIBLE);//Log.e(TAG, "handleActionDownEvenet : isHit" + isHit);return isHit;}//回退动画时间间隔值 private static int BACK_DURATION = 20 ; // 20ms //水平方向前进速率private static float VE_HORIZONTAL = 0.7f ; //0.1dip/ms //判断松开手指时,是否达到末尾即可以开锁了 , 是,则开锁,否则,通过一定的算法使其回退。private void handleActionUpEvent(MotionEvent event){int x = (int) event.getX() ;Log.e(TAG, "handleActionUpEvent : x -->" + x + " getRight() " + getRight() );//距离在15dip以内代表解锁成功。boolean isSucess= Math.abs(x - getRight()) <= 15 ;if(isSucess){ Toast.makeText(mContext, "解锁成功", 1000).show(); resetViewState(); virbate(); //震动一下 //结束我们的主Activity界面 mainHandler.obtainMessage(MainActivity.MSG_LOCK_SUCESS).sendToTarget();}else {//没有成功解锁,以一定的算法使其回退 //每隔20ms , 速率为0.6dip/ms , 使当前的图片往后回退一段距离,直到到达最左端mLastMoveX = x ; //记录手势松开时,当前的坐标位置。int distance = x - tv_slider_icon.getRight() ;//只有移动了足够距离才回退Log.e(TAG, "handleActionUpEvent : mLastMoveX -->" + mLastMoveX + " distance -->" + distance );if(distance >= 0) mHandler.postDelayed(BackDragImgTask, BACK_DURATION);else{ //复原初始场景resetViewState();}}}//重置初始的状态,显示tv_slider_icon图像,使bitmap不可见private void resetViewState(){mLastMoveX = 1000 ;tv_slider_icon.setVisibility(View.VISIBLE);invalidate(); //重绘最后一次}//通过延时控制当前绘制bitmap的位置坐标private Runnable BackDragImgTask = new Runnable(){public void run(){//一下次Bitmap应该到达的坐标值mLastMoveX = mLastMoveX - (int)(BACK_DURATION * VE_HORIZONTAL);Log.e(TAG, "BackDragImgTask ############# mLastMoveX " + mLastMoveX);invalidate();//重绘//是否需要下一次动画 ? 到达了初始位置,不在需要绘制boolean shouldEnd = Math.abs(mLastMoveX - tv_slider_icon.getRight()) <= 8 ;if(!shouldEnd) mHandler.postDelayed(BackDragImgTask, BACK_DURATION);else { //复原初始场景resetViewState();}}};private Handler mHandler =new Handler (){public void handleMessage(Message msg){Log.i(TAG, "handleMessage : #### " );}};//震动一下下咯private void virbate(){Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);vibrator.vibrate(200);}public void setMainHandler(Handler handler){mainHandler = handler;//activity所在的Handler对象}}
补充一下,我们的Activity采用了“singleTop”启动模式,防止启动多个Activity实例。
继续分享示例DEMO源代码,希望能帮助你的学习。示例DEMO下载地址:
http://download.csdn.net/detail/qinjuning/4295410
PS:如果绝对本文对你有帮助,请给顶一下子。
- Android自定义锁屏实现----仿正点闹钟滑屏解锁
- Android自定义锁屏实现----仿正点闹钟滑屏解锁
- Android自定义锁屏实现----仿正点闹钟滑屏解锁
- Android自定义锁屏实现----仿正点闹钟滑屏解锁
- Android 仿小米锁屏实现九宫格解锁
- Android 实现自定义闹钟
- Android 实现自定义闹钟
- Android 实现自定义闹钟
- Android学习笔记(一)之仿正点闹钟时间齿轮滑动的效果
- Android 自定义view 高仿小米闹钟
- Android仿苹果iphone数字锁屏解锁功能
- Android 仿IOS解锁锁屏文字抖动效果
- Android自定义View 闹钟唤起播放闹钟铃声实现
- Android学习 - 自定义锁屏demo之滑动解锁
- android service实现循环定时提醒(仿闹钟)
- Android 仿闹钟 及 通知栏的实现
- 仿正点闹钟的时钟拨轮效果(ScrollView 的无限循环滚动)
- 【Android】自定义控件实现九宫格解锁
- windows下重装mysql删除注册表键值
- c2-3-main-frm_10701-10900渲染日志
- Tomcat6数据源配置
- VC List控件使用经验总结一
- VC List控件使用经验总结二
- Android自定义锁屏实现----仿正点闹钟滑屏解锁
- 更改Visual Studio 2010的主题设置
- Visual Studio 快捷键
- java匿名类练习
- 关于AJAX/javascript 跨域访问的解决办法及 CORS(Cross-Origin Resource Sharing) 简单介绍
- Resetting ObjectOutputStream -Java Tutorials
- 第十三周实验--任务4--设计一个抽象类CSolid,含有两个求表面积及体积的纯虚函数。
- 每天一道面试题,为今年找工作早做准备!笨鸟先飞,哈哈(2012年5月篇)
- 网上S3C2440驱动TFT屏资料