相较于 iOS,安卓在 Q 之前并没有原生支持护眼模式,所以我们得自己实现一套。
其实网上代码也挺多的,甚至有些公司的产品就是这个功能。
先来讲下实现的思路,不想先听思路的可以直接跳到代码那块,代码也不难。
1. 实现思路
- 在
Window
上覆盖一个View
,做到跨 App 及状态栏和导航栏整体覆盖 - View 的背景设置成过滤蓝光的透明色(滤光层)
- 注意好 Android 系统兼容性问题
2. 代码
创建一个 EyeCareService
,该 Service 用来添加 Window 上的滤光层
class EyeCareService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var coverLayout: FrameLayout
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val params = WindowManager.LayoutParams().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY or
WindowManager.LayoutParams.TYPE_STATUS_BAR
} else {
this.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
}
this.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
this.format = PixelFormat.TRANSLUCENT
}
windowManager.defaultDisplay.apply {
params.gravity = Gravity.START or Gravity.TOP
val point = Point()
this.getRealSize(point)
//params.width = point.x 在华为某些新机型横屏全屏时失效,
//只覆盖一半,所以增加遮罩层大小,把宽度也设置为y
params.width = point.y
params.height = point.y
}
coverLayout = FrameLayout(this)
coverLayout.setBackgroundColor(Utils.getColor(30))
windowManager.addView(coverLayout, params)
}
override fun onDestroy() {
windowManager.removeViewImmediate(coverLayout)
super.onDestroy()
}
}
滤光层设置获取颜色方法的方法,一般传入 30 即可
/**
* 过滤蓝光
*
* @param blueFilterPercent 蓝光过滤比例[10-80]
*/
public static @ColorInt
int getColor(int blueFilterPercent) {
int realFilter = blueFilterPercent;
if (realFilter < 10) {
realFilter = 10;
} else if (realFilter > 80) {
realFilter = 80;
}
int a = (int) (realFilter / 80f * 180);
int r = (int) (200 - (realFilter / 80f) * 190);
int g = (int) (180 - (realFilter / 80f) * 170);
int b = (int) (60 - realFilter / 80f * 60);
return Color.argb(a, r, g, b);
}
activity 中调用启动方法
private fun openEyeCareMode() {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) { //有悬浮窗权限开启服务绑定 绑定权限
val intent = Intent(this, EyeCareService::class.java)
startService(intent)
} else { //没有悬浮窗权限,去开启悬浮窗权限
try {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE)
} catch (e: Exception) {
e.printStackTrace()
}
}
} else { //默认有悬浮窗权限 但是 华为, 小米,oppo等手机会有自己的一套Android6.0以下 会有自己的一套悬浮窗权限管理 也需要做适配
val intent = Intent(this, EyeCareService::class.java)
startService(intent)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
//权限授予失败,无法开启悬浮窗
return
} else {
//权限授予成功
}//有悬浮窗权限开启服务绑定 绑定权限
}
val intent = Intent(this, EyeCareService::class.java)
startService(intent)
}
}
3. 实际效果
4. 后话
剩下一些考虑点,在这我就不贴代码了,稍微讲下
- 需要实现关闭逻辑,startService 时 intent传递下关闭参数
- 考虑前台服务,防止退入后台过段时Service被系统直接杀死,导致功能失效