一个原生 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 欢迎提,但不保证响应速度。


