Android自定义锁屏实现----仿正点闹钟滑屏解锁

http://blog.csdn.net/qinjuning/article/details/7558588


 前几周看了下解锁的框架,基本上算是弄了个脸熟。看着别人花哨的解锁界面,心里也很痒痒的。于是,画了一天时间,

 捣鼓出了这个成果----仿正点闹钟解锁。基本功能实现了,但程序效率问题以及程序的几处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>节点 , 大家请注意下。

 

[java]  view plain copy print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical" android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent" >  
  4.   
  5.     <SliderRelativeLayout android:id="@+id/slider_layout"  
  6.         android:layout_width="fill_parent"  
  7.         android:layout_height="63dip"   
  8.         android:layout_gravity="center"  
  9.         android:layout_marginTop="200dip"  
  10.         android:background="@drawable/step2_tip_2"  
  11.         >  
  12.         <!-- Lock OK icon  -->  
  13.         <ImageView android:id="@+id/getup_finish_ico"  
  14.             android:layout_width="wrap_content"  
  15.             android:layout_height="wrap_content"  
  16.             android:layout_marginRight="10dip"  
  17.             android:layout_alignParentRight="true"  
  18.             android:layout_centerVertical="true"  
  19.             android:background="@drawable/slider_ico_alarm">  
  20.         </ImageView>  
  21.   
  22.   
  23.         <!-- Arrow Animation -->  
  24.         <ImageView android:id="@+id/getup_arrow"  
  25.             android:layout_width="wrap_content"  
  26.             android:layout_height="wrap_content"  
  27.             android:layout_marginRight="20dip"  
  28.             android:layout_toLeftOf="@id/getup_finish_ico"  
  29.             android:layout_alignTop="@id/getup_finish_ico"  
  30.             android:background="@anim/slider_tip_anim">  
  31.         </ImageView>  
  32.   
  33.         <!-- hint_unlock -->  
  34.         <TextView  android:layout_width="wrap_content"  
  35.             android:layout_height="wrap_content"   
  36.             android:layout_toLeftOf="@id/getup_arrow"  
  37.             android:layout_alignTop="@id/getup_finish_ico"  
  38.             android:text="@string/hint_unlock">  
  39.         </TextView>  
  40.           
  41.         <!-- slider img -->  
  42.         <TextView  android:id="@+id/slider_icon"  
  43.             android:layout_width="wrap_content"  
  44.             android:layout_height="wrap_content"   
  45.             android:layout_marginRight="5dip"  
  46.             android:drawableTop="@drawable/getup_slider_ico_normal">  
  47.         </TextView>  
  48.     </SliderRelativeLayout>  
  49.   
  50. </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代码:

[java]  view plain copy print ?
  1. public class SliderRelativeLayout extends RelativeLayout {  
  2.   
  3.     private static String TAG = "SliderRelativeLayout";  
  4.   
  5.     private TextView tv_slider_icon = null// 初始控件,用来判断是否为拖动?  
  6.     private Bitmap dragBitmap = null//拖拽图片  
  7.     private Context mContext = null// 初始化图片拖拽时的Bitmap对象  
  8.       
  9.     private Handler mainHandler = null//与主Activity通信的Handler对象  
  10.       
  11.   
  12.     private int mLastMoveX = 1000;  //当前bitmap应该绘制的地方 , 初始值为足够大,可以认为看不见   
  13.       
  14.     public SliderRelativeLayout(Context context) {  
  15.         super(context);  
  16.         mContext = context;  
  17.         initDragBitmap();  
  18.     }  
  19.     // 初始化图片拖拽时的Bitmap对象  
  20.     private void initDragBitmap() {  
  21.         if (dragBitmap == null)  
  22.             dragBitmap = BitmapFactory.decodeResource(mContext.getResources(),  
  23.                     R.drawable.getup_slider_ico_pressed);  
  24.     }  
  25.       
  26.     @Override  
  27.     protected void onFinishInflate() {  
  28.         // TODO Auto-generated method stub  
  29.         super.onFinishInflate();  
  30.         // 该控件主要判断是否处于滑动点击区域。滑动时 处于INVISIBLE状态(消失),正常时处于VISIBLE(可见)状态  
  31.         tv_slider_icon = (TextView) findViewById(R.id.slider_icon);  
  32.     }  
  33.     public boolean onTouchEvent(MotionEvent event) {  
  34.         int x = (int) event.getX();  
  35.         int y = (int) event.getY();  
  36.         Log.i(TAG, "onTouchEvent" + " X is " + x + " Y is " + y);  
  37.         switch (event.getAction()) {  
  38.         case MotionEvent.ACTION_DOWN:  
  39.             mLastMoveX = (int) event.getX();  
  40.             //处理Action_Down事件:  判断是否点击了滑动区域  
  41.             return handleActionDownEvenet(event);  
  42.         case MotionEvent.ACTION_MOVE:  
  43.             mLastMoveX = x; //保存了X轴方向  
  44.             invalidate(); //重新绘制                  
  45.             return true;  
  46.         case MotionEvent.ACTION_UP:  
  47.             //处理Action_Up事件:  判断是否解锁成功,成功则结束我们的Activity ;否则 ,缓慢回退该图片。  
  48.             handleActionUpEvent(event);  
  49.             return true;  
  50.         }  
  51.         return super.onTouchEvent(event);  
  52.     }  
  53.   
  54.     // 绘制拖动时的图片  
  55.     public void onDraw(Canvas canvas) {  
  56.         super.onDraw(canvas);         
  57.         //Log.(TAG, "onDraw ######" );  
  58.         // 图片更随手势移动  
  59.         invalidateDragImg(canvas);  
  60.     }  
  61.   
  62.     // 图片更随手势移动  
  63.     private void invalidateDragImg(Canvas canvas) {  
  64.         //Log.e(TAG, "handleActionUpEvenet : invalidateDragImg" );  
  65.         //以合适的坐标值绘制该图片  
  66.         int drawXCor = mLastMoveX - dragBitmap.getWidth();  
  67.         int drawYCor = tv_slider_icon.getTop();  
  68.         Log.i(TAG, "invalidateDragImg" + " drawXCor "+ drawXCor + " and drawYCor" + drawYCor);  
  69.         canvas.drawBitmap(dragBitmap,  drawXCor < 0 ? 5 : drawXCor , drawYCor , null);  
  70.     }  
  71.   
  72.     // 手势落下是,是否点中了图片,即是否需要开始移动  
  73.     private boolean handleActionDownEvenet(MotionEvent event) {  
  74.         Rect rect = new Rect();  
  75.         tv_slider_icon.getHitRect(rect);  
  76.         boolean isHit = rect.contains((int) event.getX(), (int) event.getY());  
  77.           
  78.         if(isHit)  //开始拖拽 ,隐藏该图片  
  79.             tv_slider_icon.setVisibility(View.INVISIBLE);  
  80.           
  81.         //Log.e(TAG, "handleActionDownEvenet : isHit" + isHit);  
  82.           
  83.         return isHit;  
  84.     }  
  85.   
  86.     //回退动画时间间隔值   
  87.     private static int BACK_DURATION = 20 ;   // 20ms  
  88.     //水平方向前进速率  
  89.     private static float VE_HORIZONTAL = 0.7f ;  //0.1dip/ms  
  90.       
  91.     //判断松开手指时,是否达到末尾即可以开锁了 , 是,则开锁,否则,通过一定的算法使其回退。  
  92.     private void handleActionUpEvent(MotionEvent event){          
  93.         int x = (int) event.getX() ;      
  94.         Log.e(TAG, "handleActionUpEvent : x -->" + x + "   getRight() " + getRight() );  
  95.         //距离在15dip以内代表解锁成功。  
  96.         boolean isSucess= Math.abs(x - getRight()) <= 15 ;  
  97.           
  98.         if(isSucess){  
  99.            Toast.makeText(mContext, "解锁成功"1000).show();  
  100.            resetViewState();      
  101.            virbate(); //震动一下  
  102.            //结束我们的主Activity界面  
  103.            mainHandler.obtainMessage(MainActivity.MSG_LOCK_SUCESS).sendToTarget();  
  104.         }  
  105.         else {//没有成功解锁,以一定的算法使其回退  
  106.             //每隔20ms , 速率为0.6dip/ms ,  使当前的图片往后回退一段距离,直到到达最左端     
  107.             mLastMoveX = x ;  //记录手势松开时,当前的坐标位置。  
  108.             int distance = x - tv_slider_icon.getRight() ;  
  109.             //只有移动了足够距离才回退  
  110.             Log.e(TAG, "handleActionUpEvent : mLastMoveX -->" + mLastMoveX + " distance -->" + distance );  
  111.             if(distance >= 0)  
  112.                 mHandler.postDelayed(BackDragImgTask, BACK_DURATION);  
  113.             else{  //复原初始场景  
  114.                 resetViewState();  
  115.             }  
  116.         }  
  117.     }  
  118.     //重置初始的状态,显示tv_slider_icon图像,使bitmap不可见  
  119.     private void resetViewState(){  
  120.         mLastMoveX = 1000 ;  
  121.         tv_slider_icon.setVisibility(View.VISIBLE);  
  122.         invalidate();        //重绘最后一次  
  123.     }  
  124.       
  125.     //通过延时控制当前绘制bitmap的位置坐标  
  126.     private Runnable BackDragImgTask = new Runnable(){  
  127.           
  128.         public void run(){  
  129.             //一下次Bitmap应该到达的坐标值  
  130.             mLastMoveX = mLastMoveX - (int)(BACK_DURATION * VE_HORIZONTAL);  
  131.               
  132.             Log.e(TAG, "BackDragImgTask ############# mLastMoveX " + mLastMoveX);  
  133.               
  134.             invalidate();//重绘         
  135.             //是否需要下一次动画 ? 到达了初始位置,不在需要绘制  
  136.             boolean shouldEnd = Math.abs(mLastMoveX - tv_slider_icon.getRight()) <= 8 ;            
  137.             if(!shouldEnd)  
  138.                 mHandler.postDelayed(BackDragImgTask, BACK_DURATION);  
  139.             else { //复原初始场景  
  140.                 resetViewState();     
  141.             }                 
  142.         }  
  143.     };  
  144.     private Handler mHandler =new Handler (){         
  145.         public void handleMessage(Message msg){           
  146.             Log.i(TAG, "handleMessage :  #### " );            
  147.         }  
  148.     };  
  149.     //震动一下下咯  
  150.     private void virbate(){  
  151.         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);  
  152.         vibrator.vibrate(200);  
  153.     }  
  154.     public void setMainHandler(Handler handler){  
  155.         mainHandler = handler;//activity所在的Handler对象  
  156.     }  
  157. }  

 

      补充一下,我们的Activity采用了“singleTop”启动模式,防止启动多个Activity实例。


      继续分享示例DEMO源代码,希望能帮助你的学习。示例DEMO下载地址:


                       http://download.csdn.net/detail/qinjuning/4295410



      PS:如果绝对本文对你有帮助,请给顶一下子。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值