给设备开发一套精美的壁纸也是一件不容易的事情
一、前言
在Android设备中壁纸的存在有一定的意义:主要体现在以下几个方面:
- 增强用户归属感和个性化表达:个性化壁纸能够帮助用户在众多设备中找到自己的位置,尤其是在公共场合或多人聚会时,独特的壁纸背景成为了个人身份的象征,增强了用户的归属感。此外,通过选择不同的壁纸背景,用户可以展示自己的审美品味和兴趣爱好,从而在社交场合中留下深刻印象。
- 提升使用体验和心情愉悦:一个美观、和谐的壁纸能够显著提升用户的整体使用体验。每当用户解锁手机时,看到心仪的壁纸,心情都会随之愉悦1。此外,治愈系的壁纸能够有效舒缓心中的焦虑和压力,看到喜欢的图案时,心情仿佛得到了一次温暖的拥抱,感受到生活的美好。
- 记录生活点滴和情感寄托:手机壁纸不仅是视觉上的装饰,更是情感和记忆的载体。用户可以选择自己或亲朋好友的照片作为壁纸,展现个人生活,增加手机与用户之间的情感联系13。许多用户还会选择一些激励人心的名言或励志句子作为屏保,时刻提醒自己追求目标,保持积极向上的态度。
- 文化与节日的象征:在不同节日或特殊时刻,更换手机壁纸也成为一种庆祝方式。例如,新年时更换喜庆的烟花、生肖图案等壁纸,既是对过去一年的告别,也是对未来的美好期许,体现了人们对时间流转的敏感与尊重4。
- 技术进步与用户体验的融合:随着技术的发展,Android系统提供了丰富的壁纸资源,包括静态和动态壁纸、布景主题等,用户可以根据自己的喜好选择合适的壁纸,使手机整体风格更加协调15。此外,息屏常显(AOD)壁纸功能也让常亮屏幕更个性化,避免了单调的黑色背景。
今天主要分享一下:
壁纸,主题,锁屏是如何开发的?
主要包含:
- 静态壁纸的开发
- 动态壁纸的开发
- 主题包开发
- 锁屏开发
二、 静态壁纸
静态壁纸开发其实很简单:
- 需要注册壁纸设置权限:
<uses-permission android:name="android.permission.SET_WALLPAPER" />
2. 通过图片bitmap,或者resource直接设置:
val resources = context.resources
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ava2)
WallpaperManager.getInstance(context).setBitmap(bitmap)
三、动态壁纸
动态壁纸的开发相对静态壁纸开发稍微麻烦了一点点
1. 注册权限相关配置
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<queries>
<intent>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent>
</queries>
2. 注册动态壁纸服务类
<service
android:name=".wallpager.WXWallpaperService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wx_wallpaper" />
</service>
- 配置壁纸resource资源
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:thumbnail="@drawable/ava3" />
3. 编写壁纸服务类代码:继承 WallpaperService
类创建动态壁纸服务,通过 Engine
实现动态渲染逻辑,如下:
class WXWallpaperService : WallpaperService() {
override fun onCreateEngine() = MyWallpaperEngine()
inner class MyWallpaperEngine : Engine() {
private val handler = Handler(Looper.getMainLooper())
private val drawRunner = Runnable { draw() }
private var visible = false
override fun onVisibilityChanged(visible: Boolean) {
this.visible = visible
if (visible) handler.post(drawRunner)
else handler.removeCallbacks(drawRunner)
}
private fun draw() {
var canvas: Canvas? = null
try {
canvas = surfaceHolder.lockCanvas()
canvas.apply {
drawColor(Color.rgb((0..255).random(), (0..255).random(), (0..255).random()))
}
} finally {
canvas?.let { surfaceHolder.unlockCanvasAndPost(it) } // 必须解锁
}
handler.removeCallbacks(drawRunner)
if (visible) handler.postDelayed(drawRunner, 1000)
}
}
}
4. 若需支持视频转动态壁纸功能,可集成 MediaPlayer
实现解码,需处理视频分辨率适配(建议限制为 4K 以下)。
surfaceHolder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
mediaPlayer.setSurface(holder.surface)
mediaPlayer.prepareAsync()
}
})
5. 设置壁纸生效:
1)自 Android 12 起,Google 为增强安全性,禁止应用直接通过代码设置动态壁纸组件,必须通过系统壁纸选择器交互完成,只能使用 ACTION_CHANGE_LIVE_WALLPAPER
Intent 跳转系统界面,由用户手动确认设置
val intent = Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER).putExtra(
WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, ComponentName(context, WXWallpaperService::class.java)
)
context.startActivity(intent)
2)Android 12 以下,可通过setWallpaperComponent设置替换系统壁纸,需要添加权限:
<uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
然后设置生效:
WallpaperManager.getInstance(context).setWallpaperComponent(ComponentName(context, WXWallpaperService::class.java))
四、主题开发
主题开发里面包含了:
- 壁纸开发
- 还包含主题包开发
- 锁屏样式定制
- 在
res/values/styles.xml
中定义Theme.LockScreen
主题属性,也可以定义其他SystemUI相关主题熟悉
<style name="Theme.LockScreen" parent="Theme.DeviceDefault">
<item name="android:windowBackground">@drawable/lock_bg</item>
<item name="android:keyguardPreviewLayout">@layout/custom_lock</item>
</style>
- 通过共享存储实现主题切换:
object ThemeManager {
private const val KEY_BG = "lock_bg"
private const val KEY_STYLE = "lock_style"
fun applyTheme(context: Context, bgResId: Int) {
context.getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
putInt(KEY_BG, bgResId)
apply()
}
context.sendBroadcast(Intent("LOCK_THEME_CHANGED"))
}
fun getCurrentBg(context: Context): Int {
return context.getSharedPreferences("theme_prefs", MODE_PRIVATE)
.getInt(KEY_BG, R.drawable.default_bg)
}
}
五、锁屏样式开发
系统锁屏开发涉及到KeyguardService
这个类,KeyguardService
是Android系统中管理锁屏界面的核心服务类。
它是作为SystemUI
组件的一部分运行在system_server
进程,
通过KeyguardServiceDelegate
进行服务绑定和生命周期管理,
使用bindServiceAsUser
方式避免多用户场景下的重复启动问题。
系统源码SystemUI里面有相关锁屏服务代码
class MiKeyguardService : KeyguardService() {
private lateinit var windowManager: WindowManager
private lateinit var keyguardView: View
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
setupKeyguardView()
}
private fun setupKeyguardView() {
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
WindowManager.LayoutParams.FLAG_FULLSCREEN or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT
)
keyguardView = LayoutInflater.from(this)
.inflate(R.layout.mi_lock, null).apply {
findViewById<ImageButton>(R.id.mi_unlock).setOnClickListener {
disableKeyguard()
}
}
windowManager.addView(keyguardView, params)
}
override fun onDestroy() {
windowManager.removeView(keyguardView)
super.onDestroy()
}
相关锁屏服务注册配置信息
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.milockscreen">
<uses-permission android:name="android.permission.BIND_KEYGUARD" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
<service
android:name=".MiKeyguardService"
android:label="MIUI Custom Lock"
android:permission="android.permission.BIND_KEYGUARD">
<intent-filter>
<action android:name="android.service.keyguard.KeyguardService" />
</intent-filter>
</service>
</application>
</manifest>
- 三方锁屏开发步骤:
1. 新建普通service服务,下面用compose方式实现:
class WXKeyguardService : LifecycleService(), SavedStateRegistryOwner, ViewModelStoreOwner {
/**
* Build our saved state registry controller
*/
private val savedStateRegistryController: SavedStateRegistryController by lazy(LazyThreadSafetyMode.NONE) {
SavedStateRegistryController.create(this)
}
/**
* Build our view model store
*/
private val internalViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
/**
* Context dedicated to the view
*/
private val overlayContext: Context by lazy {
val defaultDisplay: Display = getSystemService(DisplayManager::class.java).getDisplay(Display.DEFAULT_DISPLAY)
createDisplayContext(defaultDisplay).createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null)
}
override val savedStateRegistry: SavedStateRegistry
get() = savedStateRegistryController.savedStateRegistry
override val viewModelStore: ViewModelStore
get() = internalViewModelStore
// Access our window manager
private val windowManager by lazy {
overlayContext.getSystemService(WindowManager::class.java)
}
private val composeView by lazy {
ComposeView(overlayContext)
}
private val screenOffReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_SCREEN_OFF -> showLockScreen()
Intent.ACTION_USER_PRESENT -> removeLockScreen()
}
}
}
override fun onCreate() {
super.onCreate()
savedStateRegistryController.performRestore(null)
composeView.setViewTreeLifecycleOwner(this)
composeView.setViewTreeViewModelStoreOwner(this)
composeView.setViewTreeSavedStateRegistryOwner(this)
composeView.setContent { Content() }
registerReceiver(screenOffReceiver, IntentFilter(Intent.ACTION_SCREEN_OFF))
}
// 在Service或Activity中执行
private fun disableSystemKeyguard() {
val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
val lock = keyguardManager.newKeyguardLock("CustomLock")
lock.disableKeyguard() // 需SYSTEM_ALERT_WINDOW和DISABLE_KEYGUARD权限 :ml-citation{ref="3" data="citationList"}
}
private fun showLockScreen() {
disableSystemKeyguard()
val flag: Int = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT
)
windowManager.addView(composeView, params)
}
private fun removeLockScreen() {
windowManager.removeView(composeView)
}
@Composable
private fun Content() {//示例作品就写了一张图片
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
removeLockScreen()
}
.background(Color.Red)
) {
Image(
modifier = Modifier
.wrapContentHeight()
.wrapContentWidth(), painter = painterResource(id = R.drawable.ava2), contentDescription = ""
)
}
}
override fun onDestroy() {
unregisterReceiver(screenOffReceiver)
windowManager.removeView(composeView)
super.onDestroy()
}
}
2. 配置服务注册和相关权限
<service
android:name=".lockscreen.WXKeyguardService"
android:enabled="true"
android:exported="true">
</service>
注册悬浮窗权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
申请悬浮窗权限
if (!Settings.canDrawOverlays(context)) {
(context as Activity).startActivityForResult(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
data = "package:${context.packageName}".toUri()
}, 3000
)
} else {
startService(Intent(context, WXKeyguardService::class.java))
}
六、总结
本文主要分享了Android中 静态壁纸的开发, 动态壁纸的开发。 主题包开发, 锁屏开发等