Android 锁屏原理,锁屏框架与锁屏开发

Android框架浅析之锁屏(Keyguard)机制原理

锁屏、状态栏、Launcher---姑且称之为“IDLE”小组,或许叫手机美容小组

1、分析锁屏界面的组成 ;

2、基于源代码分析锁屏相关类 ;

3、提出一种在框架取消锁屏的方法 。

本文分析版本具体是Android2.3版本。

源文件路径主要有两个:

frameworks\base\policy\src\com\android\internal\policy\impl\   ---->锁屏框架

frameworks\base\core\java\com\android\internal\widget\          ----> 提供了一些的自定义View.


 一、锁屏界面的组成


       通常 Android手机上大家常见的界面只有一种,成功后即可解锁进入界面了。其实在Android手机中,正常的锁屏界面由

 两种不同性质的界面组成:


    第一种界面称之为LockScreen界面(为了叙述方便,我们姑且称为“解锁界面),即我们通常所见到的界面,手机厂商一般定制

          该界面。界面如下所示:

                                                 


          该界面对应自定义View的是LockScreen.java类

           路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\LockScreen.java

               

   第二种界面称之为UnLockScreen(为了后文叙述方便,我们姑且称为“开锁界面”),一般由Android源码提供,有如下四种

             ①、图案开锁界面 ---- PatternUnlockScreen.java类     (自定义LinearLayout)

                      路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\PatternUnlockScreen.java

                      界面显示为:

                                                        


             ②、PIN开锁界面 ---- SimUnlockScreen.java 类           (自定义LinearLayout)

                      路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\SimUnlockScreen.java

                      界面显示为:   (图片省略)

                                             

             ③、密码开锁界面 ---- PasswordUnlockScreen.java类   (自定义LinearLayout)

                      路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\PasswordUnlockScreen.java

                      界面显示为:

                                              


             ④、GoogleAccount 开锁界面 ,即Google账户开锁界面。一般用于当用户输入密码错误次数超过上限值时,系统会提示

       你输入Google账户去开锁。注意:开启它需要你手动设置账户与同步,否则该界面是不会出来的。

                    对应的源文件是: AccountUnlockScreen.java类    (自定义LinearLayout)

                    路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\AccountUnlockScreen.java   

                    界面显示为:

                                                              


    可以按照如下办法选择开启哪一种开锁界面: 设置—>位置和安全—>设置屏幕锁定  ,具体选择那种开锁界面。

 

    显示规则

         当然,这两种界面的组合也是有很多变化的,总的规则如下:

            首先显示LockScreen界面,接着判断是否开启了UnLockScreen界面,如果设置了UnLockScreen界面,则进入对应的

    UnLockScreen界面去解锁,才算成功解锁。但,存在一种特殊的情况,就是假如我们选择了图案  UnLockScreen界面,是不会

    显示LockScreen界面,而只会显示UnLockScreen界面。


二、锁屏界面的实现

       任何一种界面都是由各种View/ViewGroup(当然包括自定义的)组成的,然后根据系统对应的状态值的改变去更新

  这些View的显示状态,锁屏界面自然也是如此。锁屏界面的实现最顶层是采用了FrameLayout去控制的,当然内部也嵌套了很

  多层,内嵌层数的增多的一点好处就是我们可以分开而治,具体针对每层去做相应的更新。


       当界面复杂时,Google为开发人员提供的一款优秀工具---Hierarchy Viewer ,通过它,我们很清晰的弄明白整

  个View树的继承层次,一个布局结构,当然,看源代码也是必须的。

       

Hierarchy Viewer介绍开始


在Android的SDK工具包中,有很多十分有用的工具,可以帮助程序员开发和测试Android应用程序,大大提高其工作效率。其中的一款叫Hierachy Viewer的可视化调试工具,可以很方便地在开发者设计,调试和调整界面时,提高用户的开发效率。本文将以一个实际例子讲解如何使用该款工具运用在Android的开发过程中。本文的读者对象为具备初步Android知识的用户。

   步骤1 设计界面

   在我们的这个例子中,有三个不同的界面,以方便我们演示使用Hierarchy Viewer。每一个界面都使用了LinearLayout和FrameLayout布局,以及文本框TextView及图片框ImageView控件。如下图,三个界面中的图案分别用了一只小猫,一个鱼缸,一条金鱼,它们各自的位置布局见下图:

Android 实用工具Hierarchy Viewer实战


  在上面的三个图中,最左面的一个图,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是一个TextView文本框,里面的文字是“Safe”,第2行是一个FrameLayout帧布局,分别包含了一条鱼和一个鱼缸子。

中间的图中,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是写有“Unsafe”文本的文本框,第二行也有一个LinearLayout的水平布局,分别又包含了两个ImageView控件:一个鱼缸及鱼,还有一只小猫。

最右边的图中,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是写有“Yum” 文本的文本框,第2行是一个FrameLayout帧布局,分别包含了一条小猫和一条鱼。跟第一张图有点相象。

   步骤2 启动应用程序

   在设计界面后,我们直接用模拟器启动我们的应用程序,要注意的是,在debug状态下,是不能启动Hierachy Viewer的。

   步骤3 启动Hierachy Viewer

   目前,在eclipse的ADT Android插件中,还不能启动Hierachy Viewer,但可以从Android SDK工具包中,通过命令行的方式可以启动,具体方法为,到Android SDK下的tools目录下,在命令行方式下运行hierachyviewer即可。

   在启动后,可以看到如下的界面,会显示当前正在运行中的模拟器的信息,这里我们可以鼠标点击我们已经启动了的Activity:

Android 实用工具Hierarchy Viewer实战


  同时可以看到,有两个按钮,分别代表两个功能:

   1)Load View Hierarchy (可以查看界面的控件层次)

   2)Inspect Screenshot (进入界面精确查看模式)

   下面分别介绍两者的功能。

步骤4 Inspecting Screenshots(界面精确查看模式)

   先点Inspecting Screenshots按钮,进入界面精确查看模式。在这个模式下,开发者可以随意点界面的任意一部分,进行放大或缩小观察以查看界面中各控件的具体位置和情况,如下图所示:

Android 实用工具Hierarchy Viewer实战


  同时,还可以将截取的界面另外保存为PNG格式的图片文件。


步骤5 Load View Hierarchy 查看界面的控件层次

   接下来,我们重点学习如何在Load View Hierachy中查看界面中各个控件的层次结构关系。首先当点Load View Hierarchy按钮后,会进入如下图所示界面:

Android 实用工具Hierarchy Viewer实战


  要注意的是,在屏幕的左下方,有三个按钮,当点最左边的按钮时,返回的是模拟器的列表界面(也就是刚进入Hierarchy Viwer的界面),中间按钮则是Load View Hierachy的主界面,用户可以在这两种状态中来回切换。

   接下来,我们看上图,Load View Hierachy界面被划分为四个部分,分别是最左边(面积最大一块),该部分显示界面控件的层次结构,我们称之为主窗口,而右上方的一个部分,是以缩略图的方式显示整个应用中的各控件的层次关系,当如果一个界面中的控件比较多的时候,可以通过鼠标在这个显示区域进行移动,则左边的主窗口中会具体显示相关的控件信息。右边区域的中间部分,显示的每个控件的具体属性,是控件的属性面版。而右下角部分的区域,则是当用户点界面中的某个控件时,会在该部分显示区域,显示出用户所点的控件,在界面中的具体位置,会用红色部分标出,方便用户辨识。

步骤6 理解Hierachy Viewer的主窗口

   最左部分的主窗口,将一个Activity中的所有控件的层次结构从左到右显示了出来,其中最右部分是最低一层的控件。用我们的例子来说,如果选择了第一个界面(即上文提到的三张图界面的最左边的一张),在主窗口的最右边,从右往左看,可以看到最右边的是两个ImageView控件:鱼缸的图片和小鱼的图片。

   再往左看,会看到这两个控件实际上是被包裹在FrameLayout布局中,这里可以清楚看到指出了这个布局的id为@id/frameLayoutFishbowl。再往左边看,可以看到再上一层的控件:LinearLayout布局控件以及它包含的一个TextView控件(显示“Safe”字样)以及@id/frameLayoutFishbowl的frameLayout布局控件。

   读者可以尝试,在Hierachy viewer中,查看另外两个activity界面中的控件,熟悉其中的用法。

   步骤7 查看每个具体控件的情况

   当在主窗口中,点击每一个控件时,将会可以看到很多关于这个控件的详细信息,会在该控件的上方弹出一个窗口,其中会显示该控件的实际的效果图外,通过view的数目显示了该控件及其子控件的数目,该控件的该节点的测量(measure)、布局(layout)以及画视图(draw)的时间,如下图:

Android 实用工具Hierarchy Viewer实战


  如上图,这里1 view表明这个文本控件没再包含其他子控件了,只有1个就是它本身。而下方的带颜色的三个圆圈指示灯,分别说明了在测量(measure)、布局(layout)以及画视图(draw)三个阶段,这个控件所占用的时间百分比,如果是绿色的,表示该控件在该阶段比起其他的50%的控件的速度要快,为黄色的表示比起其他的50%的控件的速度要慢,为红色的则表示该控件在该阶段的处理速度是最慢的,如下图:

Android 实用工具Hierarchy Viewer实战


  当我们按“display View”按钮后,在当我们点某个控件时,在稍等1-2秒后,会另外单独打开一个小窗口,显示该空间的单独效果图。

   我们再来看下右上角的缩略显示窗口。当界面里的控件太多时,可以在这个窗口中,点选某一部分,随即会在主窗口中显示该区域控件的情况,而在右方中部的属性列表中,会显示所点击的控件的详细属性情况。右下角则显示所点击的控件在整个界面中的实际位置,如果勾选了show extra views,则还会在这个区域中,将控件的实际图片也显示出来,十分清晰。下图是我们点金鱼这个图片时,实际显示的效果:

Android 实用工具Hierarchy Viewer实战


  步骤8 刷新显示

   要注意的是,在Hierarchy Viewer中,当修改了界面后,需要手工点Refresh按钮,才能同步在Hierarchy Viewer中显示更新过后的界面情况。下图是分别对应用中的第2,第3个界面进行操作的示意图,可以看到,这两个界面的布局比第一个界面稍微复杂了。

Android 实用工具Hierarchy Viewer实战

Android 实用工具Hierarchy Viewer实战


  对于Android的UI来说,invalidate和requestLayout是最重要的过程,Hierarchyviewer提供了帮助我们Debug特定的UI执行invalidate和requestLayout过程的途径,方法很简单,只要选择希望执行这两种操作的View点击按钮就可以。当然,我们需要在例如onMeasure()这样的方法中打上断点。这个功能对于UI组件是自定义的非常有用,可以帮助单独观察相关界面显示逻辑是否正确。


Hierarchy Viewer介绍结束


       整个锁屏界面的继承层次如下(部分以及设置了图案开锁界面),更加完整的图请使用Hierarchy Viewer 工具查看。

      上图中比较重要的几个视图说明如下:

       LockPatternKeyguardView 继承至FrameLayout :作为LockScreen和UnLockScreen的载体,用来控制显示LockScreen

                      还是UnLockScreen界面。

       LockScreen 继承至FrameLayout   

       PatterUnlockScreen    ViewGroup类型 : 图案解锁界面

       KeyguardViewHost继承至FrameLayout, 该ViewGroup作为顶层View,作为WindowManager的装饰对象添加至窗口。 它和LockPatternKeyguardView关系相当于DecorView和我们Activity内设置的资源布局一样。


三、锁屏机制的类结构说明


        接下来我们分析下一些主要的类及其重要的函数,更多函数实现。


     1、 KeyguardScreen 类    接口    


功能:该接口的主要功能是为每个需要显示的界面:LockScreen或者UnLockScreen定义了四个方法,使其在不同的状态能够得到相应处理。优点就是:利用设计原则的面向接口编程,减少对具体对象的依赖。

路径:\frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreen.java

其源代码释义如下:

[java]  view plain copy print ?
  1. /** 
  2.  * Common interface of each {@link android.view.View} that is a screen of 
  3.  * {@link LockPatternKeyguardView}. 
  4.  */   
  5. public interface KeyguardScreen {  
  6.     /** Return true if your view needs input, so should allow the soft 
  7.      * keyboard to be displayed. */  
  8.     boolean needsInput(); //View是否需要输入数值,即该界面需要键盘输入数值   
  9.     /** This screen is no longer in front of the user.*/  
  10.     void onPause();//当该界面不处于前台界面时调用,包括处于GONE或者该界面即将被remove掉  
  11.     /** This screen is going to be in front of the user. */  
  12.     void onResume();//相对于onPause()方法,当该界面不处于前台界面时调用,处于VISIBLE状态时调用  
  13.     /** This view is going away; a hook to do cleanup. */  
  14.     void cleanUp();//该界面即将被remove掉 ,即不在需要  
  15. }  


      2、KeyguardScreenCallback类  接口

功能:每个需要显示的界面:LockScreen或者UnLockScreen都保存了该对象的唯一实例,用来向控制界面汇报情况。

路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreenCallback.java

其源代码释义如下:

[java]  view plain copy print ?
  1. /** Within a keyguard, there may be several screens that need a callback 
  2.  * to the host keyguard view. 
  3.  */   
  4. public interface KeyguardScreenCallback extends KeyguardViewCallback {  
  5.     /** Transition to the lock screen*/  
  6.     void goToLockScreen();  //当前界面跳转为LockScreen ,而不是UnLockScreen  
  7.     /** Transition to the unlock screen.*/  
  8.     void goToUnlockScreen();//LockScreen成功开锁 ,是否需要显示UnLockScreen,否则,直接开锁成功。  
  9.     //忘记了开锁图案,即我们需要跳转到Google 账户去开锁。  
  10.     void forgotPattern(boolean isForgotten);  
  11.     boolean isSecure();//当前机器是否安全,例如:设置了图案、密码开锁等     
  12.     //该函数还不太懂,可能是是否只需要验证UnlockScreen界面,即可成功开锁。  
  13.     boolean isVerifyUnlockOnly();  
  14.     /**Stay on me, but recreate me (so I can use a different layout).*/  
  15.     void recreateMe(Configuration config); //重新根据手机当前状态,显示对应的Screen.  
  16.     /** Take action to send an emergency call. */  
  17.     void takeEmergencyCallAction();  //紧急呼叫时的处理行为.  
  18.   
  19.     /** Report that the user had a failed attempt to unlock with password or pattern.*/  
  20.     void reportFailedUnlockAttempt(); //在UnLockScreen界面登陆失败时处理  
  21.   
  22.     /** Report that the user successfully entered their password or pattern.*/  
  23.     void reportSuccessfulUnlockAttempt();//在UnLockScreen界面登陆成功时处理  
  24.   
  25.     /** Report whether we there's another way to unlock the device. 
  26.      * @return true */  
  27.     boolean doesFallbackUnlockScreenExist();  
  28. }  
其唯一实现类位于LockPatternKeyguardView类的内部类。


   3、KeyguardViewCallback类  接口


        功能: 提供了一些接口用来接受用户操作Screen的结果。

        路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewCallback.java

        其源代码释义如下:

[java]  view plain copy print ?
  1. /** 
  2.   * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}  
  3.   * various things. 
  4.   */  
  5.  public interface KeyguardViewCallback {  
  6.   
  7.      /** Request the wakelock to be poked for the default amount of time. */  
  8.      void pokeWakelock();  //保存屏幕在一定时间内处于亮屏状况 , 默认时间为5s或者10s  
  9.      /** Request the wakelock to be poked for a specific amount of time.  */  
  10.      void pokeWakelock(int millis);//根据给定时间值,使屏幕在该事件段内保持亮屏状况  
  11.   
  12.      /** Report that the keyguard is done. 
  13.       * @param authenticated Whether the user securely got past the keyguard. 
  14.       *   the only reason for this to be false is if the keyguard was instructed 
  15.       *   to appear temporarily to verify the user is supposed to get past the 
  16.       *   keyguard, and the user fails to do so. */  
  17.      //成功的完成开锁,可以进入手机界面了。参数为ture表示是否正大光明的开锁,例如:图案正确,密码输入正确。  
  18.      void keyguardDone(boolean authenticated);   
  19.      /**Report that the keyguard is done drawing. */  
  20.      void keyguardDoneDrawing(); //整个锁屏界面draw()过程绘制完成时,回调该方法.  
  21.  }  

其唯一实现类是   KeyguardViewMediator


     4、 KeyguardWindowController类 接口

功能:提供通用 接口,判断该界面是否需要显示输入法窗口。

        其源代码释义如下:

[java]  view plain copy print ?
  1. /** 
  2.  * Interface passed to the keyguard view, for it to call up to control 
  3.  * its containing window. 
  4.  */  
  5. public interface KeyguardWindowController {  
  6.     /** Control whether the window needs input -- that is if it has 
  7.      * text fields and thus should allow input method interaction. */  
  8.     void setNeedsInput(boolean needsInput);  //是否需要显示输入法,为true表示需要。该方法可以想上层报到是否需要显示输入法窗口  
  9. }  

其唯一实现类是KeyguardViewManager类。

       

5、KeyguardViewManager类 


功能:包装了WindowManager功能了,提供了添加、删除锁屏界面的功能。

其源代码释义如下:     

[java]  view plain copy print ?
  1.  public class KeyguardViewManager implements KeyguardWindowController {  
  2.      ...  
  3.      private WindowManager.LayoutParams mWindowLayoutParams;  
  4.      private boolean mNeedsInput = false//是否需要输入法 , 默认不需要  
  5.   
  6.      private FrameLayout mKeyguardHost;   //该ViewGroup作为顶层View,作为WindowManager添加至窗口  
  7.      private KeyguardViewBase mKeyguardView; //具体窗口内容。  
  8.      //以上两种的关系相当于DecorView和我们Activity内设置的资源文件一样  
  9.   
  10.      private boolean mScreenOn = false//是否处于亮屏状态  
  11.      //构造函数,初始化各种属性  
  12.      public KeyguardViewManager(Context context, ViewManager viewManager,  
  13.              KeyguardViewCallback callback, KeyguardViewProperties keyguardViewProperties, KeyguardUpdateMonitor updateMonitor) {  
  14.         ...  
  15.      }  
  16.      /** 
  17.       * Helper class to host the keyguard view. 
  18.       */  
  19.      private static class KeyguardViewHost extends FrameLayout {  
  20.          ... //KeyguardViewHost类  
  21.      }  
  22.   
  23.      /** 
  24.       * Show the keyguard.  Will handle creating and attaching to the view manager 
  25.       * lazily. 
  26.       */  //显示锁屏界面  
  27.      public synchronized void show() {         
  28.          if (mKeyguardHost == null) {  
  29.              ...  
  30.              mViewManager.addView(mKeyguardHost, lp);  
  31.          }  
  32.          if (mKeyguardView == null) {  
  33.              ...  
  34.              mKeyguardHost.addView(mKeyguardView, lp);  
  35.              if (mScreenOn) {  
  36.                  mKeyguardView.onScreenTurnedOn();  
  37.              }  
  38.          }  
  39.         ...  
  40.      }  
  41.      ...  
  42.   
  43.      /*** Hides the keyguard view */  
  44.      public synchronized void hide() { //隐藏锁屏界面,也就是说我们成功的解锁了  
  45.          if (mKeyguardHost != null) {  
  46.              mKeyguardHost.setVisibility(View.GONE);  
  47.                  ...  
  48.              }  
  49.      }  
  50.      //锁屏界面是否处于显示状态  
  51.      public synchronized boolean isShowing() {  
  52.          return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);  
  53.      }  
  54.  }  
  55.   
  56.   
  57. }  
  58.         

      6、 KeyguardUpdateMonitor.java类 

功能:该类的主要功能就是根据监视系统状态值的改变(例如:时间、SIM卡状态、电池电量;使用广播监听),根据这种状态值的改变回调监听了该状态信息的对象实例。

其源代码释义如下:     

[java]  view plain copy print ?
  1. public class KeyguardUpdateMonitor {  
  2.     ...  
  3.     private int mFailedAttempts = 0;  //当前登录事,已经失败的次数  
  4.     private ArrayList<InfoCallback> mInfoCallbacks; //保存所有监听对象 InfoCallback  
  5.     private ArrayList<SimStateCallback> mSimStateCallbacks ; //保存所有监听对象  SimStateCallback  
  6.     private static class SimArgs {  //Sim状态信息  
  7.       ...  
  8.     }  
  9.     /** 
  10.      * Callback for general information relevant to lock screen. 
  11.      */  
  12.     interface InfoCallback {  
  13.         //电池电量信息改变:参数含义分别如下:是否显示电量信息  、 是否插入电影充电、 当前电池电量值  
  14.         void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);  
  15.         void onTimeChanged(); //时间发生了改变  
  16.         //网络运营商状态发生了改变 ,例如从中国移动2G变为中国移动3G,或者无服务等 ;  
  17.         void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);   
  18.         /** Called when the ringer mode changes. */  
  19.         void onRingerModeChanged(int state);  
  20.         /** 电话状态发生了改变  值可能为:EXTRA_STATE_IDLE、EXTRA_STATE_RINGING、EXTRA_STATE_OFFHOOK*/  
  21.         void onPhoneStateChanged(String newState);  
  22.     }  
  23.     /** Callback to notify of sim state change. */  
  24.     interface SimStateCallback {  
  25.         void onSimStateChanged(IccCard.State simState); //Sim卡信息发生了改变,例如有正常状况变为ABSENT/MISSING状态  
  26.     }  
  27.   
  28.     /*** Register to receive notifications about general keyguard information 
  29.      * (see {@link InfoCallback}. */  
  30.     public void registerInfoCallback(InfoCallback callback) {  
  31.         if (!mInfoCallbacks.contains(callback)) {  
  32.             mInfoCallbacks.add(callback);  //注册一个监听器  
  33.         } ...  
  34.     }  
  35.    ...  
  36.  }  
      7, LockPatternKeyguardView类  (自定义ViewGroup)

功能:作为LockScreen和UnLockScreen界面的载体,控制显示哪个界面。

其源代码释义如下:     

[java]  view plain copy print ?
  1. public class LockPatternKeyguardView extends KeyguardViewBase {  
  2.     ...  
  3.     private View mLockScreen;  
  4.     private View mUnlockScreen;  
  5.   
  6.     private boolean mScreenOn = false;//是否亮屏  
  7.   
  8.     enum Mode {  
  9.         //当前显示界面的Mode Lock 或者UnLock  
  10.     }  
  11.     enum UnlockMode {  
  12.         ...//开锁界面的几种不同Mode   
  13.     }  
  14.     //构造函数  
  15.     public LockPatternKeyguardView( ...) {       
  16.         //KeyguardScreenCallback的实现对象  
  17.         mKeyguardScreenCallback = new KeyguardScreenCallback() {  
  18.            ...  
  19.         };  
  20.         ...  
  21.     }  
  22.     public void reset() {  
  23.        ...//重置显示界面  
  24.     }  
  25.     private void recreateLockScreen() {  
  26.     ...//重新构建LockScreen  
  27.     }  
  28.     private void recreateUnlockScreen() {  
  29.        ...//重新构建UnlockScreen  
  30.     }  
  31.     private void recreateScreens() {  
  32.        ...//重新构建该视图  
  33.     }  
  34.     public void verifyUnlock() {  
  35.        ...   
  36.     }  
  37.     public void cleanUp() {  
  38.     ... //清理资源对象  
  39.     }  
  40.     private boolean isSecure() {  
  41.        ...//手机设置是否处于安全状态  
  42.     }  
  43.     private void updateScreen(final Mode mode) {  
  44.        ...//根据参数(Lock/unLock),判断显示为LockScreen或者UnlockScreen界面  
  45.     }  
  46.     View createLockScreen() {  
  47.     ...//创建lockScreen  
  48.     }  
  49.     View createUnlockScreenFor(UnlockMode unlockMode) {  
  50.        ...//根据不同的Unlock Mode , 创建不同的UnlockScreen  
  51.     }  
  52.     private Mode getInitialMode() {  
  53.        ...//得到初始化的状态Mode (lock or unlock).  
  54.     }  
  55.     /** Given the current state of things, what should the unlock screen be? */  
  56.     private UnlockMode getUnlockMode() {  
  57.        ...//返回开锁的状态Unlock Mode  
  58.     }  
  59.     private void showTimeoutDialog() {  
  60.         ... //输入密码超过一定次数时,提示30s后在登录的对话框  
  61.     }  
  62.     private void showAlmostAtAccountLoginDialog() {  
  63.        ... //显示Google 账户登录对话框  
  64.     }  
  65. }  

       8、KeyguardViewBase类 抽象类  (自定义ViewGroup)

功能:为LockPatternKeyguardView提供了一组通用的方法 。需要值得注意的方法就是他对某些KeyEvent的监听,当他消费监听到这些KeyEvent,App就监听不到这些KeyEvent了 。常用的有KEYEVENT_VOLUME_UP/DOWN等。

[java]  view plain copy print ?
  1. public abstract class KeyguardViewBase extends FrameLayout {  
  2.      ...  
  3.   @Override  
  4.     public boolean dispatchKeyEvent(KeyEvent event) {  
  5.         ...  
  6.         if (interceptMediaKey(event)) {  
  7.             return true;  
  8.         }  
  9.         return super.dispatchKeyEvent(event);  
  10.     }  
  11.   
  12.     private boolean interceptMediaKey(KeyEvent event) {  
  13.         final int keyCode = event.getKeyCode();  
  14.         if (event.getAction() == KeyEvent.ACTION_DOWN) {  
  15.             switch (keyCode) {  
  16.                 ...//more keyevent  
  17.                 case KeyEvent.KEYCODE_VOLUME_UP:  
  18.                 case KeyEvent.KEYCODE_VOLUME_DOWN: {  
  19.                     ...  
  20.                     // Don't execute default volume behavior  
  21.                     return true//直接返回不在向下传递处理  
  22.                 }  
  23.             }  
  24.         }   
  25.         return false;  
  26.     }  
  27.  }  

9、 KeyguardViewProperties.java 接口

功能:提供了创建界面的通用方法。

[java]  view plain copy print ?
  1.   public interface KeyguardViewProperties {    
  2.     //创建一个KeyguardViewBase实例 , 实际是指LockPatternKeyguardView实例  
  3.     KeyguardViewBase createKeyguardView(Context context,  
  4.             KeyguardUpdateMonitor updateMonitor,  
  5.             KeyguardWindowController controller);  
  6.   
  7.     boolean isSecure();  
  8. }       

          其唯一实现类是是LockPatternKeyguardViewProperties类。


10、LockPatternKeyguardViewProperties类 

源代码释义如下:

[java]  view plain copy print ?
  1. public class LockPatternKeyguardViewProperties implements KeyguardViewProperties {  
  2.    ...  
  3.    //创建一个LockPatternKeyguardView对象  
  4.    public KeyguardViewBase createKeyguardView(Context context,  
  5.            KeyguardUpdateMonitor updateMonitor,  
  6.            KeyguardWindowController controller) {  
  7.        return new LockPatternKeyguardView(context, updateMonitor,  
  8.                mLockPatternUtils, controller);  
  9.    }  

11、KeyguardViewMediator核心类 ,该类是唯一实现了KeyguardViewCallback的类。

功能:  功能:该类提供了一些接口,由PhoneWindowManager)去访问控制Keyguard.... 

该类的初始化是在PolicyWindowManager的构造函数中创建的。如下:

[java]  view plain copy print ?
  1. public class PhoneWindowManager implements WindowManagerPolicy {  
  2.    ...  
  3.   /** {@inheritDoc} */  //由SystemServer调用  
  4.   public void init(Context context, IWindowManager windowManager,  
  5.           LocalPowerManager powerManager) {  
  6.       ...//初始化该实例  
  7.       mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);  
  8.   }  
  9.  }  
  10.    

参照源代码,把一些重要的属性和方法的大意给分析下:

[java]  view plain copy print ?
  1. public class KeyguardViewMediator implements KeyguardViewCallback, KeyguardUpdateMonitor.SimStateCallback {  
  2.     private boolean mSystemReady;  //启动成功 由SystemServer调用  
  3.   
  4.     /**Used to keep the device awake while to ensure the keyguard finishes opening before 
  5.      * we sleep.*/ //在需要显示锁屏界面时,保持屏幕在某个时间段内为暗屏状态    
  6.     private PowerManager.WakeLock mShowKeyguardWakeLock;  
  7.     private KeyguardViewManager mKeyguardViewManager; //KeyguardViewManager实例  
  8.     /**  * External apps (like the phone app) can tell us to disable the keygaurd.*/  
  9.     //是否允许其他App禁止锁屏 , 例如来电时 禁止锁屏  
  10.     private boolean mExternallyEnabled = true;  
  11.     //处于锁屏状态 , 即显示锁屏  
  12.     private boolean mShowing = false;  
  13.     // true if the keyguard is hidden by another window  
  14.     private boolean mHidden = false//被其他窗口掩盖 , 例如来电时锁屏被掩盖  
  15.     private boolean mScreenOn = false// 是否亮屏  
  16.   
  17.     public KeyguardViewMediator(Context context, PhoneWindowManager callback,  
  18.             LocalPowerManager powerManager) {  
  19.         ...  
  20.         //构造相关实例对象  
  21.         mKeyguardViewProperties = new LockPatternKeyguardViewProperties(  
  22.                 new LockPatternUtils(mContext), mUpdateMonitor);  
  23.   
  24.         mKeyguardViewManager = new KeyguardViewManager(  
  25.                 context, WindowManagerImpl.getDefault(), this,  
  26.                 mKeyguardViewProperties, mUpdateMonitor);  
  27.         //解锁成功后发送的Intent  
  28.         mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);  
  29.         mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);  
  30.     }  
  31.     /** Called to let us know the screen was turned off. 
  32.      *   @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, 
  33.      *   {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or 
  34.      *   {@link WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}. 
  35.      */  
  36.     //屏幕变灰暗  , 原因有如下:以及对应的逻辑处理。  
  37.     // 1、OFF_BECAUSE_OF_USER : 用户按下POWER键 , 当前是否处于锁屏界面,若是(mShowing)则重置显示界面,否则重新显示锁屏界面  
  38.     // 2、OFF_BECAUSE_OF_TIMEOUT : 屏幕超时,常见情况就是一段时间没有操作屏幕,手机处于灰暗状态。          处理行为:     
  39.     //     发送Action值为DELAYED_KEYGUARD_ACTION的广播,因为该类注册了该Intent广播,接受到时会调用doKeyguard()方法锁屏  
  40.     // 3、OFF_BECAUSE_OF_PROX_SENSOR:接打电话时,距离感应太近导致暗屏,此时由于PowerManager那儿已经处理了暗屏,不需要做任何事  
  41.     // 最后,如果以上逻辑都不成立,调用 doKeyguard()方法显示屏幕                         
  42.     public void onScreenTurnedOff(int why) {  
  43.        ...  
  44.     }  
  45.     /** 
  46.      * Let's us know the screen was turned on. 
  47.      */  
  48.     public void onScreenTurnedOn() {  
  49.         synchronized (this) {  
  50.             ...  
  51.             notifyScreenOnLocked();  //通知亮屏  
  52.         }  
  53.     }  
  54.     /** Enable the keyguard if the settings are appropriate. */  
  55.     private void doKeyguard() {  
  56.         synchronized (this) {  
  57.             ...  
  58.             showLocked();//显示锁屏界面  
  59.         }  
  60.     }  
  61.     //该方法的调用时机就是当按下POWER键时,系统会回调该方法 keyCode值一般为 26  
  62.     public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode) {  
  63.         //操作按键是否能唤醒屏幕   
  64.         if (isWakeKeyWhenKeyguardShowing(keyCode)) {  
  65.             // give the keyguard view manager a chance to adjust the state of the  
  66.             // keyguard based on the key that woke the device before poking  
  67.             // the wake lock  
  68.             wakeWhenReadyLocked(keyCode);//开始唤醒屏幕咯  
  69.             return true;  
  70.         } else {  
  71.             return false;  
  72.         }  
  73.     }  
  74.     /** {@inheritDoc} */  //在一定时间内保存屏幕为亮屏状态  
  75.     public void pokeWakelock(int holdMs) {  
  76.         ...  
  77.     }  
  78.     //表示成功得分完成了解锁操作  
  79.     public void keyguardDone(boolean authenticated, boolean wakeup) {  
  80.         synchronized (this) {  
  81.             //发送给Handler 进行异步处理  
  82.             Message msg = mHandler.obtainMessage(KEYGUARD_DONE);  
  83.             msg.arg1 = wakeup ? 1 : 0;  
  84.             mHandler.sendMessage(msg);  
  85.             if (authenticated) {  
  86.                 mUpdateMonitor.clearFailedAttempts(); //清除错误登录次数  
  87.             }  
  88.             ...  
  89.         }  
  90.     }  
  91.     //Handler对象 , 异步处理  
  92.     private Handler mHandler = new Handler() {  
  93.         @Override  
  94.         public void handleMessage(Message msg) {  
  95.             switch (msg.what) {  
  96.               ...  //异步处理  
  97.             }  
  98.         }  
  99.     };  
  100.     //异步处理完成开锁成功  
  101.     private void handleKeyguardDone(boolean wakeup) {   
  102.         handleHide(); //释放该Keyguard对应的窗口  
  103.         mWakeLock.release();  
  104.         mContext.sendBroadcast(mUserPresentIntent); //解锁成功,发送Intent信息  
  105.     }  
  106.     //显示锁屏界面  
  107.     private void handleShow() {  
  108.         synchronized (KeyguardViewMediator.this) {  
  109.             ...  
  110.             mKeyguardViewManager.show();  
  111.             mShowing = true;  
  112.             ...  
  113.         }  
  114.     }  
  115.     private void handleHide() {  
  116.         synchronized (KeyguardViewMediator.this) {  
  117.             //去除锁屏界面对应的窗口  
  118.             mKeyguardViewManager.hide();  
  119.             mShowing = false;  
  120.            ...  
  121.         }  
  122.     }  
  123.     //设置状态栏enable状态 , 例如:能否被下拉等  
  124.     private void adjustStatusBarLocked() {  
  125.             ...  
  126.             // if the keyguard is shown, allow the status bar to open  
  127.             // only if the keyguard is insecure and is covered by another window  
  128.             boolean enable = !mShowing || (mHidden && !isSecure());  
  129.             mStatusBarManager.disable(enable ?StatusBarManager.DISABLE_NONE : StatusBarManager.DISABLE_EXPAND);  
  130.         }  
  131.     }  
  132. }  

该类的很多方法都是由PhoneWindowManager调用访问的。


问题:如何在框架中, 解除锁屏 ?


基本思路:每次需要显示Keyguard---锁屏界面时,我们并不真正的去锁屏,而只是提供了一个空的方法去给系统调用,让系统觉得我们“锁屏”了,同样也不去真正的隐藏“锁屏”界面,提供一个空壳给系统调用。由于可能涉及到其它问题,例如:能否下拉状态栏,按下POWER键后,屏幕很快休眠等。Come on ,我们需要统一做处理。

所有步骤函数都发生在KeyguardViewMediator 类中,注释部分为我们所添加的。

Step 1、  取消  真正的去锁屏实现

[java]  view plain copy print ?
  1. //该方法会显示锁屏界面,我们使其成为一个空壳子  
  2. private void handleShow() {  
  3.     synchronized (KeyguardViewMediator.this) {  
  4.         if (DEBUG) Log.d(TAG, "handleShow");  
  5.         if (!mSystemReady) return;  
  6.   
  7.         playSounds(true);  
  8.         //Begin : Modifid by qinjuning       
  9.           
  10.         //mKeyguardViewManager.show();  //     
  11.         //mShowing = true;              //  
  12.         //adjustUserActivityLocked();   //  
  13.         //adjustStatusBarLocked();     //取消对状态栏的控制  
  14.           
  15.         //End  
  16.         try {  
  17.             ActivityManagerNative.getDefault().closeSystemDialogs("lock");  
  18.         } catch (RemoteException e) {  
  19.         }  
  20.         mShowKeyguardWakeLock.release();  
  21.     }  
  22. }  

        Step 2、  取消  真正的去隐藏锁屏实现       

[java]  view plain copy print ?
  1. //真正的隐藏屏幕实现  
  2. private void handleHide() {  
  3.     synchronized (KeyguardViewMediator.this) {  
  4.         if (DEBUG) Log.d(TAG, "handleHide");  
  5.         if (mWakeAndHandOff.isHeld()) {  
  6.             Log.w(TAG, "attempt to hide the keyguard while waking, ignored");  
  7.             return;  
  8.         }  
  9.         // only play "unlock" noises if not on a call (since the incall UI  
  10.         // disables the keyguard)  
  11.         if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {  
  12.             playSounds(false);  
  13.         }  
  14.         //Begin : Modifid by qinjuning    
  15.           
  16.         //mKeyguardViewManager.hide();  
  17.         //mShowing = false;   
  18.         //adjustUserActivityLocked();         
  19.         //adjustStatusBarLocked();         //取消对状态栏的控制  
  20.           
  21.         //End  
  22.     }  
  23. }  

以上两步行动后,存在一个Bug(问题),就是唤醒屏幕后,会在指定的时间内屏幕由亮变暗,我们还需要做如下修改

 Step 3、按下POWER键时, 解除屏幕由亮变暗的Bug

[java]  view plain copy print ?
  1. private void handleWakeWhenReady(int keyCode) {  
  2.     synchronized (KeyguardViewMediator.this) {  
  3.         if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")");  
  4.   
  5.         // this should result in a call to 'poke wakelock' which will set a timeout  
  6.         // on releasing the wakelock  
  7.         if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) {  
  8.             // poke wakelock ourselves if keyguard is no longer active  
  9.             Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves");  
  10.             //Begin : Modifid by qinjuning    
  11.             //pokeWakelock();  //按下POWER键时, 解除屏幕由亮变暗的Bug     
  12.             //End  
  13.         }  
  14.         /** 
  15.          * Now that the keyguard is ready and has poked the wake lock, we can 
  16.          * release the handoff wakelock 
  17.          */  
  18.         mWakeAndHandOff.release();  
  19.   
  20.         if (!mWakeLock.isHeld()) {  
  21.             Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq");  
  22.         }  
  23.     }  
  24. }  

          上面Step 1、以及Step 2可以由如下方法代替:

        将属性mExternallyEnabled 设置为 false, 接下来需要显示界面时都不会继续走下去,如下函数:

[java]  view plain copy print ?
  1. /** 
  2.  * Enable the keyguard if the settings are appropriate. 
  3.  */  //显示界面  
  4. private void doKeyguard() {  
  5.     synchronized (this) {  
  6.         // if another app is disabling us, don't show  
  7.         if (!mExternallyEnabled) {   //mExternallyEnabled 为false   
  8.             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");  
  9.   
  10.             // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes  
  11.             // for an occasional ugly flicker in this situation:  
  12.             // 1) receive a call with the screen on (no keyguard) or make a call  
  13.             // 2) screen times out  
  14.             // 3) user hits key to turn screen back on  
  15.             // instead, we reenable the keyguard when we know the screen is off and the call  
  16.             // ends (see the broadcast receiver below)  
  17.             // TODO: clean this up when we have better support at the window manager level  
  18.             // for apps that wish to be on top of the keyguard  
  19.             return;  
  20.         }  
  21.         ...  
  22.     }  
  23. }  

      该方法的一个缺点就是,假如存在重新调用了setKeyguardEnabled()设置该值,一切都是白搭( 但从源码看,这点不可能

 出现,因为存在另一个判断依据:变量mNeedToReshowWhenReenabled , 其初始值为false,只有成功禁止锁屏之后才置为

 true )。 此,我们可以仿照这个方法,主动添加一个私有变量,禁止显示锁屏界面,即禁止doKeyguard()方法继续走下去。

       显然易见,手机厂商,基于框架只需要修改LockScreen这个自定义ViewGroup即可,其他的一套Google已经为我们封装好了。

      在框架层修改肯定不是最好的,对于第三方的App而言,实现不了该功能。还好,SDK为我们提供了接口类去处理隐藏锁屏接口

的方法,该类是KeyguardManager类:

     我们可以通过KeyguardManager类实例获得一个KeyguardManager.KeyguardLock对象,进而调用相应方法去取消锁屏界面示锁屏界面。

   KeyguardManager.KeyguardLock的两个方法说明如下:

          public void disableKeyguard ()

            功能:取消锁屏界面显示,同时禁止显示锁屏界面。除非你显示调用了reenableKeyguard()方法使能显示锁屏界面。

            public void reenableKeyguard ()

            功能: 使能显示锁屏界面,如果你之前调用了disableKeyguard()方法取消锁屏界面,那么会马上显示锁屏界面。 


     这两个方法最终都会调用到KeyguardViewMediator类的setKeyguardEnabled(boolean enable)方法。

            参数说明: enable = false  对应于disableKeyguard()方法,

                             enable = true  对应于reenableKeyguard()方法。

该方法原型为: 位于KeyguardViewMediator类中

[java]  view plain copy print ?
  1. /** 
  2.  * Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide 
  3.  * a way for external stuff to override normal keyguard behavior.  For instance 
  4.  * the phone app disables the keyguard when it receives incoming calls. 
  5.  */   
  6. public void setKeyguardEnabled(boolean enabled) {  
  7.     synchronized (this) {  
  8.         mExternallyEnabled = enabled;  //保存值,该值会在doKeyguard()时用到,如果为false ,则不进行锁屏  
  9.   
  10.         if (!enabled && mShowing) {  
  11.             if (mExitSecureCallback != null) {//该判断为false  
  12.                 ...   
  13.                 return ;  
  14.             }  
  15.             mNeedToReshowWhenReenabled = true;  //置为真,以便下次调用  
  16.             hideLocked(); //已经显示了锁屏界面,则取消隐藏界面  
  17.         } else if (enabled && mNeedToReshowWhenReenabled) { //重新显示锁屏界面  
  18.             mNeedToReshowWhenReenabled = false;  
  19.   
  20.             if (mExitSecureCallback != null) {//该判断为false  
  21.                 
  22.             } else {  
  23.                 showLocked(); //显示隐藏界面  
  24.                 ...  
  25.             }  
  26.         }  
  27.     }  
  28. }  

       使用这两个方法时,记得加上如下权限android.permission.DISABLE_KEYGUARD

   为了在亮屏时,达到取消显示界面的效果,我们还需要知道 一下两个广播:

         屏幕变暗以及屏幕点亮的广播

             android.intent.action.SCREEN_ON  --- 屏幕变亮

             android.intent.action.SCREEN_OFF ---- 屏幕点暗


 于是在监听到屏幕变暗/变亮时,通过KeyguardManager 类实现即可。对与用户而言,就相当于解除了锁屏界面了。

可能代码如下:

[java]  view plain copy print ?
  1. //屏幕变暗/变亮的广播 , 我们要调用KeyguardManager类相应方法去解除屏幕锁定  
  2. private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver(){  
  3.     @Override  
  4.     public void onReceive(Context context , Intent intent) {  
  5.         String action = intent.getAction() ;  
  6.           
  7.         Log.i(TAG, intent.toString());  
  8.           
  9.         if(action.equals("android.intent.action.SCREEN_OFF")  
  10.                 || action.equals("android.intent.action.SCREEN_ON") ){  
  11.             mKeyguardManager = (KeyguardManager)context.getSystemService(Context.KEYGUARD_SERVICE);  
  12.             mKeyguardLock = mKeyguardManager.newKeyguardLock("zdLock 1");   
  13.             mKeyguardLock.disableKeyguard();  
  14.             startActivity(zdLockIntent);  
  15.         }  
  16.     }  
  17.       
  18. };  
Android自定义锁屏实现----仿正点闹钟滑屏解锁
效果图:

                                                 

                                    (我们的解锁界面)                                                        (正点闹钟的解锁界面)


Hierarchy Viewer工具查看,我们重点关注下图蓝色区域,如下:

 

                                                                

 

   上图中的蓝色区域利用Hierarchy Viewer工具得出如下的布局文件组成:


                                         


                                                 



                                                                               

                                                           (这三张图片采用了黑色背景,便于观看)  

             

其中最顶层RelativeLayout采用了一个背景图片,即圆弧形的图片。


上图的编号解释如下 :

   编号 1 :TextView控件,显示一个图片资源,可能采用了android:background或者android:drawableLeft类似属性显示图片。

   编号 2 :TextView控件 ---- “向右滑动结束提醒”

   编号 3 :ImageView控件 --- 为了显示动画效果,采用了一个<animation-list>对应的动画文件,作为该控件的背景图片,从其图片资源文件看验证了这点,采用了三张不同效果的图片。 

   编号 4 : ImageView控件,显示一张图片。


说明: 代码块中 <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)机制原理

  >的最后面所述。



    为了帮助大家更好的理解代码,最后说明图片是如何回退的?

      基本思路:我们根据当前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下载地址: http://download.csdn.net/detail/qinjuning/4295410


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值