Skip to content
AIRUIMAXPublic

About

极暗 + 护眼双层屏幕滤镜:比系统最低亮度更暗,再叠一层可调暖黄滤光|原生 Android

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

DeepDark · 极暗 / 护眼

一个原生 Android 屏幕滤镜工具:比系统最低亮度更暗,同时叠一层可调暖黄滤光护眼。两种模式相互独立,可以单开也可以同时开。

纯本地应用:不联网、不埋点、不申请任何网络权限。全部配置只存在设备自己的 SharedPreferences 里。


截图

极暗 护眼 两种模式同时开启
极暗 护眼 同时开启

左上角选项卡切换模式,右上角 ⓘ 查看使用说明。选项卡上的小圆点表示该模式当前是否开启 —— 哪怕你正在看另一个模式,也能一眼看到。


功能

极暗模式

把画面压到系统物理最低亮度之下。做法是在 WindowManager 上插一层全屏纯色浮层,用窗口 alpha 挡光。

  • 亮度调节:挡光 21% ~ 79% 连续可调
  • 遮罩颜色:6 档预设(墨黑 / 深灰 / 暖灰 / 暖褐 / 琥珀 / 冷蓝)。非纯黑会抬高黑位,但能降低对比度、减少蓝光
  • 同时压低系统背光(实验项):通过窗口的 screenBrightness 真正压低面板背光,而不只是叠一层遮罩
  • 快捷方式:系统下拉磁贴 / 通知栏常驻调节面板 / 双击音量下键
  • 开机自启:可选让两种模式在重启后自动恢复

护眼模式

在极暗之上再叠一层暖黄滤光,削弱蓝光刺激。

  • 护眼程度:滤光 21% ~ 79% 连续可调
  • 黄色微调:从淡黄 → 明黄 → 琥珀 → 深橙连续插值,另有 4 个色板一键跳转
  • 独立磁贴:下拉快捷面板里单独的「护眼」按钮,和「极暗」磁贴互不影响

两种模式的关系

两个模式是两层独立的浮层窗口,层级恒为「极暗在下、护眼在上」—— 于是黄光永远作用在已经压暗过的画面上,不会反过来被极暗层再压一次。

极暗 护眼
浮层颜色 黑 / 灰 / 暖色(6 档) 暖黄 → 深橙(可微调)
强度 挡光 21%~79% 滤光 21%~79%
作用 降低整体亮度 削弱蓝光
独立开关 ✅ ✅

技术要点

  • Kotlin + Jetpack Compose + Material 3,单 Activity,全部界面为 Compose 自定义绘制(卡片 / 开关颜色 / 滑杆配色都是自己写的,没有套用 M3 默认皮肤)
  • TYPE_APPLICATION_OVERLAY 全屏浮层,窗口 alpha 决定浓度,setBackgroundColor 决定颜色
  • 前台服务(specialUse 类型)维护浮层,退到后台不会被回收;常驻通知用自定义 RemoteViews,展开后可直接调档位
  • 最低支持 Android 8.0(API 26),编译/目标 API 35

⚠️ 两个关键的平台约束(改代码前务必先看)

1. 浮层 alpha 绝不能 ≥ 0.8。 Android 12(API 31)起,非可触控浮层的 MaximumObscuringOpacityForTouch 默认为 0.8:一旦某层 alpha 超过它,InputDispatcher 会判定为「遮挡触控(untrusted touch due to occlusion)」,直接丢弃所有落在下层窗口的触摸事件 —— 屏幕变砖,连关都关不掉。

代码里死守 Prefs.MAX_ALPHA = 0.79f。这个上限是逐窗口判定的,实测两层同时 0.79 也不会触发吞触摸(logcat | grep -ic "untrusted touch" = 0)。

2. 浮层盖不住状态栏和下拉通知栏。 dumpsys window windows 里的 mBaseLayer 决定层级:

窗口 mBaseLayer
NotificationShade 171000
StatusBar 151000
TYPE_APPLICATION_OVERLAY(本应用) 111000
普通 Activity 21000

浮层 frame 确实是全屏,但状态栏画在它上面。这是平台限制,任何同类应用都一样。想连状态栏一起压暗,只能靠「同时压低系统背光」或系统自带的极暗。

其他踩过的坑

  • PendingIntent 的唯一性只由 action / data / component 决定,不含 extras。通知栏的档位按钮必须用不同的 setData(uri) 区分,否则 5 个按钮会共用同一个。
  • 通知里放不了可拖动的 SeekBar。RemoteViews 白名单只到 ProgressBar,且明写不支持子类(SeekBar 即子类)。所以通知栏是「ProgressBar 指示 + 一排可点档位按钮」。
  • WindowManager 没有「插到某个窗口下面」的 API。同 App 同类型浮层只能靠 addView 的先后决定层级。所以切换模式时会整体移除后按「极暗 → 护眼」重新添加,保证叠放顺序恒定。
  • Box { A(); B() } 会把 A、B 叠在同一起点。回调里会 emit 多个 composable 的地方,容器必须用 Column。(这条是重构页面时踩出来的,滑杆和色板糊成一团的元凶。)
  • 下拉通知栏不会触发 Activity 的 onResume。界面要跟随通知栏 / 磁贴改动刷新,必须注册 SharedPreferences.OnSharedPreferenceChangeListener,且要用局部变量持有监听器(SharedPreferences 只存弱引用)。

编译

环境要求:JDK 17 + Android SDK(platform 35 / build-tools 35.0.0)。

# 1. 指向本机 SDK
echo "sdk.dir=/path/to/Android/Sdk" > local.properties

# 2. 出 debug 包
./gradlew :app:assembleDebug

# Windows
gradlew.bat :app:assembleDebug

产物:app/build/outputs/apk/debug/app-debug.apk

若 repo.maven.apache.org 访问困难,可在 settings.gradle.kts 的两个 repositories 块里把阿里云镜像放到最前面。本项目仓库配置已包含 Google / Maven Central 兜底。

安装后首次使用需要授予**「显示在其他应用上层」**权限,这是浮层能盖在别的应用上的前提。


项目结构

app/src/main/java/com/deepdark/extradim/
├── MainActivity.kt              # 全部界面(Compose),选项卡 + 两种模式卡片
├── Prefs.kt                     # 全局配置:档位、颜色锚点、alpha 换算
├── service/
│   ├── DimService.kt            # 核心:前台服务 + 两层浮层窗口 + 通知栏控制面板
│   └── BootReceiver.kt          # 开机自启
├── access/
│   └── VolumeKeyService.kt      # 无障碍服务:双击音量下键切换极暗
├── tile/
│   ├── DimTileService.kt        # 「极暗」快捷设置磁贴
│   └── EyeCareTileService.kt    # 「护眼」快捷设置磁贴
└── ui/theme/Theme.kt            # 深色配色方案

说明

个人自用工具,按需维护。Issue 欢迎提,但不保证响应速度。

About

极暗 + 护眼双层屏幕滤镜:比系统最低亮度更暗,再叠一层可调暖黄滤光|原生 Android

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages