双击锁屏选项定制
### 需求定义
双击锁屏这个东西需要权限,然后有时候这个东西,比如无障碍权限,有些ROM他可能用不了,所以用户想要自己选择对应的权限方式
#### 枚举数据
展示的这个双击锁屏方式用枚举
‘’’
enum class SleepMode(
@StringRes val labelResourceId: Int, // 每个枚举值带一个字符串资源 ID
) {
AUTO(labelResourceId = R.string.sleep_mode_auto), // “Automatic”
ROOT(labelResourceId = R.string.sleep_mode_root), // “Root”
ACCESSIBILITY(labelResourceId = R.string.sleep_mode_accessibility), // “Accessibility service”
DEVICE_ADMIN(labelResourceId = R.string.sleep_mode_device_admin), // “Device admin”
;
companion object {
// 拿所有枚举值,编译器维护
fun values() = enumValues<SleepMode>().toList()
// 字符串 → 枚举(DataStore 读出来时用)
// "DEVICE_ADMIN" → SleepMode.DEVICE_ADMIN
fun fromString(string: String) = values().firstOrNull { it.toString() == string }
// 枚举列表 → UI 选项列表(给 ListPreference 用)
fun entries(): List<ListPreferenceEntry<SleepMode>> = values().map {
ListPreferenceEntry(value = it) { stringResource(id = it.labelResourceId) }
}
}
}
‘’’
enumValues 是内置的这个方法,就拿到这个枚举值 Array
然后toList顺序枚举值的数组
fromString 就能从字符串拿对应的枚举对象
entries 就返回prefEntry,就展示pref能用到的这个item,就映射,返回一个value是这个对象,然后最后一个参数是lambda 的 compose
#### 存储
// 定义一个偏好:sleep_mode,类型是 SleepMode 枚举
‘’’
val sleepMode = preference(
key = stringPreferencesKey(name = “sleep_mode”), // DataStore 里的 key
defaultValue = SleepMode.AUTO, // 没存过时默认 AUTO
parse = { SleepMode.fromString(it) ?: SleepMode.AUTO }, // 读:字符串 → 枚举
save = { it.toString() }, // 写:枚举 → 字符串
)
// DataStore 磁盘上存的是 sleep_mode = “DEVICE_ADMIN” 这样的字符串
// parse/save 负责 字符串 ↔ 枚举 的互转
UI
@Composable
fun GesturePreferences(modifier: Modifier = Modifier) {
val prefs = preferenceManager2()
PreferenceLayout(…) {
// 第一组:手势配置列表(原有的,不动)
PreferenceGroup {
Item { GestureHandlerPreference(adapter = prefs.doubleTapGestureHandler.getAdapter(), …) }
Item { GestureHandlerPreference(adapter = prefs.swipeUpGestureHandler.getAdapter(), …) }
// … 其他手势
}
// 第二组:我们新加的 Sleep mode 选项
PreferenceGroup(heading = stringResource(id = R.string.sleep_mode_label)) {
Item {
ListPreference(
adapter = prefs.sleepMode.getAdapter(), // adapter:读当前值 + 写新值
entries = SleepMode.entries(), // 4 个选项的列表
label = stringResource(id = R.string.sleep_mode_label), // 标题 "Sleep mode"
)
// adapter.state.value = 当前选中的枚举值 → UI 显示对应文字
// 用户点了新选项 → adapter.onChange(新值) → DataStore 存储 → state 更新 → UI 刷新
}
}
}
}
‘’’
这里用到的就是上面的entry,要用他里面的东西。然后这个adapter就是一个拓展函数,就实际上它是用来减少模板函数和解耦的,就里面写了一个state和onChange修改state,就很像useState这个
‘’’
@Composable
fun
adapter: PreferenceAdapter
entries: List<ListPreferenceEntry
label: String,
…
) {
ListPreference(
entries = entries,
value = adapter.state.value, // 从 adapter 里拿当前值
onValueChange = adapter::onChange, // 从 adapter 里拿写回函数
label = label,
…
)
}
‘’’
上面就是大概的减少模板代码的这个
静态展示(设置页上那一行)
‘’’
// 计算当前描述文字
val currentDescription = description ?: entries
.firstOrNull { it.value == value } // 找到当前选中的 entry
?.label?.invoke() // 调它的 label lambda 拿文字
// 比如 value = DEVICE_ADMIN → 找到对应 entry → label() → “Device admin”
PreferenceTemplate(
title = { Text(text = label) }, // “Sleep mode”
description = { currentDescription?.let { Text(text = it) } }, // “Device admin”
…
)
‘’’
然后这个就是具体,用传进来的entry来展示页面
#### 具体业务
‘’’
class SleepGestureHandler(context: Context) : GestureHandler(context) {
override suspend fun onTrigger(launcher: LawnchairLauncher) {
// 从 DataStore 读用户选的值
val pref = PreferenceManager2.getInstance(context).sleepMode.get().first()
// sleepMode.get() → Flow<SleepMode>
// .first() → 取当前值,比如 SleepMode.DEVICE_ADMIN
// 根据用户选择,找到对应的实现类
val method = when (pref) {
SleepMode.AUTO -> methods.first { it.isSupported() }
// AUTO:遍历 methods,找第一个 isSupported()=true 的(原逻辑)
SleepMode.ROOT -> methods.filterIsInstance<SleepMethodRoot>().first()
// 从 list 里找 SleepMethodRoot 类型的实例
SleepMode.ACCESSIBILITY -> methods.filterIsInstance<SleepMethodPieAccessibility>().first()
SleepMode.DEVICE_ADMIN -> methods.filterIsInstance<SleepMethodDeviceAdmin>().first()
}
// 退化逻辑:用户选了具体方式但当前不支持(比如选了 ROOT 但 unroot 了)
if (pref != SleepMode.AUTO && !method.isSupported()) {
methods.first { it.isSupported() }.sleep(launcher) // 退化到自动模式
return
}
// 正常执行
method.sleep(launcher)
// SleepMethodRoot.sleep() → Shell 模拟电源键
// SleepMethodPieAccessibility.sleep() → 无障碍服务锁屏(没开会弹引导)
// SleepMethodDeviceAdmin.sleep() → DevicePolicyManager 锁屏(没激活会弹引导)
}
// 三种息屏实现,原有代码不动
private val methods = listOf(
SleepMethodRoot(context),
SleepMethodPieAccessibility(context),
SleepMethodDeviceAdmin(context),
)
sealed class SleepMethod(protected val context: Context) {
abstract suspend fun isSupported(): Boolean
abstract suspend fun sleep(launcher: LawnchairLauncher)
}
}
‘’’
这里就是双击具体触发,就从pref里面找,auto就找到第一个能用的,其他的就找这个具体类型的
大概总结一下,就是写了个要展示的数据,然后加到pref里面,展示了一下,然后有这个双向绑定的能力。然后双击的时候就拿这个东西,看选的哪个能不能用来锁屏。





