创建Android动态壁纸

我需要在电话屏幕上显示有关他的状况的技术信息,更确切地说,是关于他在测试池中的状况的技术信息。 我一直希望在主屏幕上看到此信息,并且没有其他身体动作。


只有两种方式不会影响其他应用程序的执行:小部件或动态壁纸。 我选择了动态壁纸,它们也称为“动态壁纸”,因为它们会自动进入主屏幕的所有页面,甚至进入锁定屏幕。 本文提供了有关如何创建动态壁纸的实用技巧。


寻找真相


关于“动态壁纸”猫的文档哭了。 自9年前在博客上首次(也是唯一一次) 宣布以来,Google尚未就此主题做出任何可理解的示例或代码实验室。 我必须明白


第一基础。 Android的内部机制使得我们只能在设备上安装应用程序,并且所有应用程序的设备都是相同的。 由于“动态壁纸”也是一个应用程序,因此控件组件的选择不是很大,我们应该期望它将是“服务”。 找到它很容易:它是WallpaperService


“动态壁纸”可能有多个实例,它们的生命周期将与“活动”或“视图”的生命周期不同。 因此,应该再有一个基类。 这是WallpaperService.Engine (并且对于WallpaperService必不可少!)。 如果仔细观察,结果将发现它是与Activity,Service和其他类似活动一样的生命周期事件提供者。


“动态壁纸”的生命周期如下所示:


onCreate(SurfaceHolder surfaceHolder)
onSurfaceCreated(SurfaceHolder持有人)
onSurfaceChanged(SurfaceHolder持有人,整数格式,整数宽度,整数高度)
onVisibilityChanged(布尔值可见)
onSurfaceRedrawNeeded(SurfaceHolder持有人)
onSurfaceDestroyed(SurfaceHolder持有人)
onDestroy()


从此列表中可以清楚地知道何时可以/需要重画图片(如果有动画则开始重画),以及何时该停止所有活动而不浪费电池。


onSurfaceRedrawNeeded()方法在其余部分中脱颖而出,请参见下文。 还有一个isVisible()方法来提供帮助(在Kotlin中将其转换为isVisible属性)。


现在您可以构建此构造函数。 我将从头开始。


抽奖


我们将不得不在Canvas上吸引自己,我们将没有任何布局和充气。 如何从SurfaceHolder获取Canvas以及如何利用它不在本文的讨论范围之内,下面有一个简单的示例。


 fun dummyDraw(c: Canvas) { c.save() c.drawColor(Color.CYAN) c.restore() } // surfaceHolder property is actually a call to the getSurfaceHolder() method fun drawSynchronously() = drawSynchronously(surfaceHolder) fun drawSynchronously(holder: SurfaceHolder) { if (!isVisible) return var c: Canvas? = null try { c = holder.lockCanvas() c?.let { dummyDraw(it) } } finally { c?.let { holder.unlockCanvasAndPost(it) } } } 

发动机寿命周期方法


onSurfaceRedrawNeeded之外的所有生命周期方法均不需要立即重绘。 因此,一个好的基调是重画队列。


 override fun onSurfaceCreated(holder: SurfaceHolder?) { super.onSurfaceCreated(holder) redrawHandler.planRedraw() } override fun onSurfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { super.onSurfaceChanged(holder, format, width, height) redrawHandler.planRedraw() } override fun onVisibilityChanged(visible: Boolean) { super.onVisibilityChanged(visible) redrawHandler.planRedraw() } override fun onSurfaceDestroyed(holder: SurfaceHolder?) { super.onSurfaceDestroyed(holder) redrawHandler.omitRedraw() } override fun onDestroy() { super.onDestroy() redrawHandler.omitRedraw() } override fun onSurfaceRedrawNeeded(holder: SurfaceHolder) { super.onSurfaceRedrawNeeded(holder) redrawHandler.omitRedraw() drawSynchronously(holder) // do it immediately, don't plan } 

注意onSurfaceRedrawNeeded ,它使我们可以调用相同名称的SurfaceHolder回调,该回调在调整大小和发生类似事件时发生。 此回调使您可以立即重绘,而无需允许用户显示旧的(并且已经不正确的)图片。 系统保证在此方法返回之前,将暂停屏幕输出。


排程器


我喜欢重新定义Handler,而不是在其中运行Runnable。 我认为,如此优雅。


如果您有动画或常规更新,则需要对消息进行常规排队( postAtTime()postDelayed()为您提供帮助)。 如果数据是偶尔更新的,则只需调用planRedraw()即可对其进行更新。


 val redrawHandler = RedrawHandler() inner class RedrawHandler : Handler(Looper.getMainLooper()) { private val redraw = 1 fun omitRedraw() { removeMessages(redraw) } fun planRedraw() { omitRedraw() sendEmptyMessage(redraw) } override fun handleMessage(msg: Message) { when (msg.what) { redraw -> drawSynchronously() else -> super.handleMessage(msg) } } } 

服务与引擎


来自Service and Engine的mareshka的组装方式如下:


 class FooBarWallpaperService : WallpaperService() { override fun onCreateEngine() = FooBarEngine() inner class FooBarEngine : Engine() { .... } } 

AndroidManifest和其他咒语


我将软件开发中的咒语称为无法理解,但是有必要准确地重复。


.../app/src/main/res/xml必须有一个描述“动态壁纸”的XML文件。 必须在AndroidManifest中指定此文件的名称(在下面的示例中查找foobarwallpaper这个词)


 <?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/some_drawable_preview" android:description="@string/wallpaper_description" /> 

不要在Service的描述中丢失permissionmeta-dataintent-filter


 <service android:name=".FooBarWallpaperService" android:enabled="true" android:label="Wallpaper Example" android:permission="android.permission.BIND_WALLPAPER"> <meta-data android:name="android.service.wallpaper" android:resource="@xml/foobarwallpaper" > </meta-data> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService"> </action> </intent-filter> </service> 

如何添加


隐藏“动态壁纸”,因此有所提示。 我描述了它在三星上的外观。


首先,长按主屏幕上的某个位置,手机将进入桌面设置模式,并显示墙纸图标。


我们单击“ 墙纸”图标,然后单击几个部分,我们需要“ 我的墙纸” ,然后单击该部分右上角的题词“ 查看全部 ”,列表将全屏打开。


我们按菜单调用中的“三点”,其中有“生活壁纸”项目(我有一个),出现可用的“动态壁纸”列表。


选择我们的壁纸,然后选择“主屏幕和锁定屏幕”。


将出现一个“预览”,已经被我们的应用程序绘制了(要识别这一刻,有isPreview()方法),单击“ 设置为墙纸” ...,我们什么也看不到,因为我们返回了可用墙纸列表。


点击“首页”享受。


然后是Android Watch ?!


一路有趣的观察是,Android Watch中的Faces的制作方法完全相同(它们具有自己的基类和自己的实现方式):相同的Service + Engine ,几乎相同的元数据和清单Service的Intent过滤器 (在墙纸这个词出现了四次:),您还需要根据Handler编写自己的sheduler


Watch Faces的基类中,有一个现成的onDraw() ,将Canvas传递到其中,并且有invalidate()来调用它。 但这不是根本的区别,而是样板的实现部分。


与动态壁纸不同,有一些“表盘”示例,您可以对其进行深入研究( 此处链接,一开始)。


发生什么事了


涂成绿色屏幕的应用程序的屏幕截图毫无意义。 但是,据此得出的几张照片是在扰流板下方的战斗台上完成的。


几张照片



贴纸是上一代剩下的问题检测系统。


致谢


如果没有这两篇文章,我会在黑暗中徘徊更长的时间。 我无法想象在如此高质量的文档中,如何在2010年就编写出它们?


Kirill Grouchnikov,动态壁纸
Vogella,Android动态壁纸-教程


源代码


GitHub

Source: https://habr.com/ru/post/zh-CN460951/


All Articles