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

  
vivie 当前离线

高级会员

Rank: 4

高级会员, 积分 553, 距离下一级还需 447 积分
注册时间
2012-5-7
最后登录
2012-5-24
阅读权限
50
积分
553
精华
1
帖子
73
查看详细资料

0

收听

4

听众

71

主题
楼主
发表于 2012-5-23 15:26:16 | 查看: 653 | 回复: 11
本帖最后由 vivie 于 2012-5-23 16:37 编辑

前几周看了下解锁的框架,基本上算是弄了个脸熟。看着别人花哨的解锁界面,心里也很痒痒的。于是,画了一天时间,捣鼓出了这个成果----仿正点闹钟解锁。基本功能实现了,但程序效率问题以及程序的几处Bug都没有完全解决,留待以后有机会弄吧。

            与正点闹钟对比如下:
1336741336_3070.jpg
2012-5-23 14:09 上传
下载附件 (23.79 KB)
1336739254_7572.png
2012-5-23 14:09 上传
下载附件 (130.74 KB)


                (我们的解锁界面)                                   (正点闹钟的解锁界面)
   如何知晓正点闹钟的布局构成?
      毫无疑问,当我们想模仿任何一个优秀界面设计时,我们的第一想法肯定是能不能看到该App Apk包对应的资源文件(一般说,编译成Apk时,src目录源文件是看不到的,res目录可以看到)。但是,为了安全性以及技术壁垒,公司都会进行加密处理。于是,我们想通过直接看layout布局文件泡汤了。当然,如果反编译看到那就无敌了。我不懂反编译,我就不多谈了。就我所知Apk下图片资源是不会加密的,于是我们可以查看这些图片以及文件名来猜测界面的可能组成情况,这对我们后面的分析很有用处。

   真正我想说的:
      一直以来,作为程序员我们一直疏忽了一个利器:--- Hierarchy Viewer工具,可以直接观看布局文件的构成。对于该工具的具体用法请参考该文章:Android 实用工具Hierarchy Viewer实战

PS : 如果你还羡慕其他人的布局 , 你就out了。  - -



     我们针对正点闹钟的如下界面,利用工具查看,我们重点关注下图蓝色区域,如下:


上图中的蓝色区域利用Hierarchy Viewer工具得出如下的布局文件组成:
1336739368_7844.png
2012-5-23 14:09 上传
下载附件 (52.14 KB)


怎么样,是不是很清楚了?大概知道怎么布局了吧。针对上面的布局,再逐一详解下,最后形成一个类似的layout布局文件。
1336739799_3699.png
2012-5-23 14:09 上传
下载附件 (32.74 KB)

首先贴下这几张资源图片吧,大家心里也有个印象。
1336739523_2422.png
2012-5-23 14:09 上传
下载附件 (8.95 KB)
1336739535_8319.png
2012-5-23 14:09 上传
下载附件 (8.88 KB)
未命名.jpg
2012-5-23 16:11 上传
下载附件 (2.43 KB)
1336739594_2423.png
2012-5-23 14:09 上传
下载附件 (1.13 KB)
1336739666_5248.png
2012-5-23 14:09 上传
下载附件 (4.17 KB)

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


    上图的编号解释如下 :
   编号 1 :TextView控件,显示一个图片资源,可能采用了android:background或者android:drawableLeft类似属性显示图片。
   编号 2 :TextView控件 ---- “向右滑动结束提醒”。so easy
   编号 3 :ImageView控件 --- 为了显示动画效果,采用了一个<animation-list>对应的动画文件,作为该控件的背景图片,
            从其图片资源文件看验证了这点,采用了三张不同效果的图片。 so easy
   编号 4 : ImageView控件,显示一张图片。 so easy

  呵呵,怎么样是不是看起来比较简单,该layout布局文件如下:

      说明: 代码块中 <SliderRelativeLayout>节点实则为<RelativeLayout>节点 , 大家请注意下。
  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.         <SliderRelativeLayout android:id="@+id/slider_layout"
  5.             android:layout_width="fill_parent"
  6.                 android:layout_height="63dip"
  7.                 android:layout_gravity="center"
  8.                 android:layout_marginTop="200dip"
  9.                 android:background="@drawable/step2_tip_2"
  10.                 >
  11.         <!-- Lock OK icon  -->
  12.                 <ImageView android:id="@+id/getup_finish_ico"
  13.                         android:layout_width="wrap_content"
  14.                     android:layout_height="wrap_content"
  15.                     android:layout_marginRight="10dip"
  16.                     android:layout_alignParentRight="true"
  17.                     android:layout_centerVertical="true"
  18.                     android:background="@drawable/slider_ico_alarm">
  19.                 </ImageView>


  20.         <!-- Arrow Animation -->
  21.         <ImageView android:id="@+id/getup_arrow"
  22.                         android:layout_width="wrap_content"
  23.                     android:layout_height="wrap_content"
  24.                     android:layout_marginRight="20dip"
  25.                     android:layout_toLeftOf="@id/getup_finish_ico"
  26.                     android:layout_alignTop="@id/getup_finish_ico"
  27.                     android:background="@anim/slider_tip_anim">
  28.             </ImageView>

  29.         <!-- hint_unlock -->
  30.         <TextView  android:layout_width="wrap_content"
  31.                     android:layout_height="wrap_content"
  32.                     android:layout_toLeftOf="@id/getup_arrow"
  33.                     android:layout_alignTop="@id/getup_finish_ico"
  34.                     android:text="@string/hint_unlock">
  35.                 </TextView>
  36.                
  37.         <!-- slider img -->
  38.         <TextView  android:id="@+id/slider_icon"
  39.             android:layout_width="wrap_content"
  40.                     android:layout_height="wrap_content"
  41.                     android:layout_marginRight="5dip"
  42.                     android:drawableTop="@drawable/getup_slider_ico_normal">
  43.                 </TextView>
  44.         </SliderRelativeLayout>

  45. </LinearLayout>
复制代码
   棘手问题: 如何实现动态效果,即绘制一个更随手指移动的东东 ? 先说下我的疑惑:如果参照正点闹钟显示的资源文件,即完全采用系统提供的控件,然后为这些控件提供触摸事件监听去滑动前面所说的编号为1的TextView控件----滑动可以调用scrollTo()或者scrollBy()方法,我试了几次但是都没有效果,原因是scrollTo()和scrollBy()方法并不偏移背景图片,仅仅偏移某个View的内容视图(onDraw()方法里绘制的),具体答案可以参考:Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解

我的实现方式

       我们知道,自定义View/ViewGroup可以唯所欲为。因此,为了实现这种拖拽效果,实现了一个继承于RelativeLayout的自定义ViewGroup控件,并且重写了其中的onTouchEvent()方法去绘制一个跟随手指而动的Bitmap图片资源。当然,这只是第一步,还有很多细节需要处理,主要是一些坐标处理以及图片随着时间返回的问题,但这块内容大家看看代码注释加以理解吧,我是真的不好描述出来。见后文所贴代码。



下面的知识主要是如何以一个APK的形式去实现解锁界面。


    知识点一 如何以APK形式去达到锁屏的目的以及 屏幕变暗以及屏幕点亮的广播。
                                   android.intent.action.SCREEN_ON  --- 屏幕变亮的广播
                                  android.intent.action.SCREEN_OFF ---- 屏幕点暗的广播
请参考:android 自定义锁屏


知识点二Activity里如何屏蔽Home键和Back键?
请参考: android实现简单锁屏


知识点三KeyguardManager简介
请参考:KeyguardManager简介
具体原因可以去看看:Android框架浅析之锁屏(Keyguard)机制原理

关于禁止下拉状态栏问题,由于不能在源码下编译,因此无法达到禁止下拉状态栏。但我们的目的主要是学习正点闹钟的布局
以及一种对它的一种可能的实现方式。这就够了。
   OK,下面贴出我的自定义RelativeLayout的代码。


    为了帮助大家更好的理解代码,最后说明图片是如何回退的?
      基本思路:我们根据当前Bitmap拖拽图片的所在位置,每隔几毫秒就让Bitmap拖拽图片回退一定距离,并且请求View会
  重新绘制,直到该Bitmap拖拽图片返回初始地方,即可显示一个回退动画效果了。代码中是利用Handler来控制的。

   自定义RelativeLayout代码:
  1. public class SliderRelativeLayout extends RelativeLayout {

  2.         private static String TAG = "SliderRelativeLayout";

  3.         private TextView tv_slider_icon = null; // 初始控件,用来判断是否为拖动?
  4.         private Bitmap dragBitmap = null; //拖拽图片
  5.         private Context mContext = null; // 初始化图片拖拽时的Bitmap对象
  6.         
  7.         private Handler mainHandler = null; //与主Activity通信的Handler对象
  8.         

  9.         private int mLastMoveX = 1000;  //当前bitmap应该绘制的地方 , 初始值为足够大,可以认为看不见        
  10.         
  11.         public SliderRelativeLayout(Context context) {
  12.                 super(context);
  13.                 mContext = context;
  14.                 initDragBitmap();
  15.         }
  16.         // 初始化图片拖拽时的Bitmap对象
  17.         private void initDragBitmap() {
  18.                 if (dragBitmap == null)
  19.                         dragBitmap = BitmapFactory.decodeResource(mContext.getResources(),
  20.                                         R.drawable.getup_slider_ico_pressed);
  21.         }
  22.         
  23.         @Override
  24.         protected void onFinishInflate() {
  25.                 // TODO Auto-generated method stub
  26.                 super.onFinishInflate();
  27.                 // 该控件主要判断是否处于滑动点击区域。滑动时 处于INVISIBLE状态(消失),正常时处于VISIBLE(可见)状态
  28.                 tv_slider_icon = (TextView) findViewById(R.id.slider_icon);
  29.         }
  30.         public boolean onTouchEvent(MotionEvent event) {
  31.                 int x = (int) event.getX();
  32.                 int y = (int) event.getY();
  33.                 Log.i(TAG, "onTouchEvent" + " X is " + x + " Y is " + y);
  34.                 switch (event.getAction()) {
  35.                 case MotionEvent.ACTION_DOWN:
  36.                         mLastMoveX = (int) event.getX();
  37.                         //处理Action_Down事件:  判断是否点击了滑动区域
  38.                         return handleActionDownEvenet(event);
  39.                 case MotionEvent.ACTION_MOVE:
  40.                         mLastMoveX = x; //保存了X轴方向
  41.             invalidate(); //重新绘制                           
  42.                         return true;
  43.                 case MotionEvent.ACTION_UP:
  44.                         //处理Action_Up事件:  判断是否解锁成功,成功则结束我们的Activity ;否则 ,缓慢回退该图片。
  45.                         handleActionUpEvent(event);
  46.                         return true;
  47.                 }
  48.                 return super.onTouchEvent(event);
  49.         }

  50.         // 绘制拖动时的图片
  51.         public void onDraw(Canvas canvas) {
  52.                 super.onDraw(canvas);               
  53.                 //Log.(TAG, "onDraw ######" );
  54.                 // 图片更随手势移动
  55.                 invalidateDragImg(canvas);
  56.         }

  57.         // 图片更随手势移动
  58.         private void invalidateDragImg(Canvas canvas) {
  59.                 //Log.e(TAG, "handleActionUpEvenet : invalidateDragImg" );
  60.                 //以合适的坐标值绘制该图片
  61.                 int drawXCor = mLastMoveX - dragBitmap.getWidth();
  62.                 int drawYCor = tv_slider_icon.getTop();
  63.                 Log.i(TAG, "invalidateDragImg" + " drawXCor "+ drawXCor + " and drawYCor" + drawYCor);
  64.             canvas.drawBitmap(dragBitmap,  drawXCor < 0 ? 5 : drawXCor , drawYCor , null);
  65.         }

  66.         // 手势落下是,是否点中了图片,即是否需要开始移动
  67.         private boolean handleActionDownEvenet(MotionEvent event) {
  68.                 Rect rect = new Rect();
  69.                 tv_slider_icon.getHitRect(rect);
  70.                 boolean isHit = rect.contains((int) event.getX(), (int) event.getY());
  71.                
  72.                 if(isHit)  //开始拖拽 ,隐藏该图片
  73.                         tv_slider_icon.setVisibility(View.INVISIBLE);
  74.                
  75.                 //Log.e(TAG, "handleActionDownEvenet : isHit" + isHit);
  76.                
  77.                 return isHit;
  78.         }

  79.         //回退动画时间间隔值
  80.         private static int BACK_DURATION = 20 ;   // 20ms
  81.     //水平方向前进速率
  82.         private static float VE_HORIZONTAL = 0.7f ;  //0.1dip/ms
  83.         
  84.     //判断松开手指时,是否达到末尾即可以开锁了 , 是,则开锁,否则,通过一定的算法使其回退。
  85.         private void handleActionUpEvent(MotionEvent event){               
  86.                 int x = (int) event.getX() ;        
  87.                 Log.e(TAG, "handleActionUpEvent : x -->" + x + "   getRight() " + getRight() );
  88.                 //距离在15dip以内代表解锁成功。
  89.                 boolean isSucess= Math.abs(x - getRight()) <= 15 ;
  90.                
  91.                 if(isSucess){
  92.                    Toast.makeText(mContext, "解锁成功", 1000).show();
  93.                    resetViewState();        
  94.                    virbate(); //震动一下
  95.                    //结束我们的主Activity界面
  96.                    mainHandler.obtainMessage(MainActivity.MSG_LOCK_SUCESS).sendToTarget();
  97.                 }
  98.                 else {//没有成功解锁,以一定的算法使其回退
  99.                     //每隔20ms , 速率为0.6dip/ms ,  使当前的图片往后回退一段距离,直到到达最左端        
  100.                         mLastMoveX = x ;  //记录手势松开时,当前的坐标位置。
  101.                         int distance = x - tv_slider_icon.getRight() ;
  102.                         //只有移动了足够距离才回退
  103.                         Log.e(TAG, "handleActionUpEvent : mLastMoveX -->" + mLastMoveX + " distance -->" + distance );
  104.                         if(distance >= 0)
  105.                             mHandler.postDelayed(BackDragImgTask, BACK_DURATION);
  106.                         else{  //复原初始场景
  107.                                 resetViewState();
  108.                         }
  109.                 }
  110.         }
  111.         //重置初始的状态,显示tv_slider_icon图像,使bitmap不可见
  112.         private void resetViewState(){
  113.                 mLastMoveX = 1000 ;
  114.                 tv_slider_icon.setVisibility(View.VISIBLE);
  115.                 invalidate();        //重绘最后一次
  116.         }
  117.         
  118.         //通过延时控制当前绘制bitmap的位置坐标
  119.         private Runnable BackDragImgTask = new Runnable(){
  120.                
  121.                 public void run(){
  122.                         //一下次Bitmap应该到达的坐标值
  123.                         mLastMoveX = mLastMoveX - (int)(BACK_DURATION * VE_HORIZONTAL);
  124.                         
  125.                         Log.e(TAG, "BackDragImgTask ############# mLastMoveX " + mLastMoveX);
  126.                         
  127.                         invalidate();//重绘               
  128.                         //是否需要下一次动画 ? 到达了初始位置,不在需要绘制
  129.                         boolean shouldEnd = Math.abs(mLastMoveX - tv_slider_icon.getRight()) <= 8 ;                        
  130.                         if(!shouldEnd)
  131.                             mHandler.postDelayed(BackDragImgTask, BACK_DURATION);
  132.                         else { //复原初始场景
  133.                                 resetViewState();        
  134.                         }                                
  135.                 }
  136.         };
  137.         private Handler mHandler =new Handler (){               
  138.                 public void handleMessage(Message msg){                        
  139.                         Log.i(TAG, "handleMessage :  #### " );                        
  140.                 }
  141.         };
  142.         //震动一下下咯
  143.         private void virbate(){
  144.                 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
  145.                 vibrator.vibrate(200);
  146.         }
  147.         public void setMainHandler(Handler handler){
  148.                 mainHandler = handler;//activity所在的Handler对象
  149.         }
  150. }
复制代码
补充一下,我们的Activity采用了“singleTop”启动模式,防止启动多个Activity实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值