在我们的 Android 手机里,你会发现,好多应用都有一个锁屏功能,当我们点亮屏幕的时候,经常就会看到这些锁屏。
它可能是这样的:
或者是这样的:
甚至是这样的:
于是我们不仅有些疑惑,这些锁屏是如何做出来的呢? 而且它们又是怎样在亮屏时显示到系统锁屏之上的呢?
稍微思考一下,聪明的你大概就已经想到了,没错,用 WindowManager 应该就可以实现啊。
既然提到 WindowManager, 我们就需要先简单了解下 WindowManager 和 Window 的一些基本知识:
Window 表示一个窗口的概念,Window 是一个抽象类,它的具体实现是 PhoneWindow。创建一个 Window,需要通过 WindowManager 即可完成。
WindowManager 是外界访问 Window 的入口,Window 具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 的过程。
Android 中,所有的视图都是通过 Window 来呈现,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上,因此 Window 是实际 View 的直接管理者,单击事件由 Window 传递给 DecorView,然后再由 DecorView 传递给我们的 View,就连 Activity 的设置视图方法 setContentView在底层也是通过 Window 来完成的。
WindowMnager 有三个比较重要的方法:
mWindowManager.addView(view,layoutParams);
mWindowManager.removeView(view,layoutParams);
mWindowManager.updateViewLayout(view,layoutParams);
分别是添加、移除和更新 View。
而 WindowManager.LayoutParams 有一个 flags 参数,其中 flags 有三个很重要的属性:
FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也不需要接收各种输入事件,最终事件会直接传递给下层的具有焦点的 Window。
FLAG_NOT_TOUCH_MODAL:在此模式下,系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他 Window 将无法收到单击事件。
FLAG_SHOW_WHEN_LOCKED:开启此模式可以让 Window 显示在锁屏的界面上。(注意,注意,做锁屏功能很关键的一个属性)
经过上面的简单介绍,我们就可以简单理下做锁屏的思路:
创建一个 BroadcastReceiver,监听系统灭屏或亮屏的广播(注意动态注册)。
创建一个 View,在这个View中添加一些我们希望展示的元素。
创建一个 Service,当系统灭屏或亮屏时,利用 WindowManager 向窗口添加 View。
给用户提供一种解锁方式,当用户解锁时,用 WindowManager 移除 View。
最后,记得在 Manifest 中声明 SYSTEM_ALERT_WINDOW 权限。
一切看上去是那么的完美,只要我们一步步照着做,锁屏就一定能出来了吧?
其实,真正做过锁屏的人一定会发现,用 WindowManager 直接实现锁屏还是有一些常见的坑的。比如,如果你的手机 Android 系统版本大于 4.4,可能就需要动态申请权限(其实有规避的方法),否则锁屏就不能显示,而且由于一些国产手机厂商的限制,即使你的 Android 系统版本小于 4.4,有可能仍然显示不出锁屏。还有就是用 WindowManager 实现锁屏稍不注意就会出现一些莫名奇妙的 Crash。
那么,有没有一种更简单,更易上手的一种实现锁屏的方式呢?
答案是肯定的。
其实我们完全可以通过用 Activity (好亲切啊,有不有?)来实现锁屏。我们只需要创建一个 Activity,然后在 Activity 的 onCreate() 方法中加入这么一句:
(是不是觉得眼熟?在介绍 WindowManager 的时候着重强调了这个 Flag)
然后再按照上面理出来的思路,保持其他流程基本不变,把 WindowManager 添加 View 变成启动 Activity;解锁时,由 WindowManager 移除View变成销毁 Activity,就能轻松实现锁屏功能,当然 SYSTEM_ALERT_WINDOW 权限也是不用声明的。
来一张对比图:
说了这么多,终于可以开始真正的实施了吧,那么,我们要打造一个怎样的锁屏呢?
必要的时间、日期、星期几 要有吧。
解锁方式一定要有点儿新意吧。
壁纸一定要非常简洁、漂亮吧。
如果再加上一些好玩的、酷炫的动画,恩,两个字:“完美!”(注意伸出你的双手比一下这个手势)。
OK,少啰嗦,先上图:
是不是觉得效果还不错?整体上看,实现的难点其实主要集中在解锁这一块儿,那么我们来分解一下:
添加 3 个循环渐变、缩放的 Ripple 圆环动画。
确定解锁区域,即底部锁屏 Icon 和 Ripple 是我们能够响应的解锁区域。
当我们在解锁区域按下的时候,让屏幕变暗,然后给用户以提示:上滑解锁。
当我们的手指从解锁区域向上移动时,根据移动的距离来画计算半径,画圆环,并且改变 View 的透明度。
当用户手指移动到一定得距离(大于解锁距离),改变提示:松手解锁。
根据用户松手的位置判断需要是否真的解锁,大于解锁距离,解锁;小于解锁距离,恢复成原样。
分解之后似乎就简单了不少,其实我们只需要自定义一个 View,然后重写 onTouchEvent,根据用户的 Touch 操作,给外部提供一些基本的回调方法。
下面贴出一些关键性的代码,回调方法主要包括如下几个:
自定义View初始化:
计算触点是否在解锁区域:
计算圆环半径和两点距离:
根据移动的距离画圆环:
重写onTouchEvent:
Activity中对回调方法的处理:
Ok,关于《教你十分钟打造一个酷炫的锁屏》就先简单介绍到这里,说是十分钟,其实十分钟一定是搞不定的啦,你懂得,哈哈哈!!!
Talk is cheap, show me the source code !
话不多说,Demo源码奉上:
https://github.com/RockySteveJobs/LockerScreen
关于Ripple 动画,感谢开源项目:
https://github.com/skyfishjy/android-ripple-background
关于粒子动画,感谢开源项目:
https://github.com/shchurov/ParticleView
文末彩蛋: