class TextClockWallpaperService : WallpaperService() {
override fun onCreateEngine(): Engine {
return MyEngine()
}
inner class MyEngine : Engine() {
/**
* 准备画笔
*/
private val mPaint = Paint().apply {
this.color = Color.RED
this.isAntiAlias = true
this.textSize = 60f
this.textAlign = Paint.Align.CENTER
}
/**
* Called to inform you of the wallpaper becoming visible or
* hidden. <em>It is very important that a wallpaper only use
* CPU while it is visible.</em>.
*
* 当壁纸显示或隐藏时会回调该方法。
* 很重要的一点是,要只在壁纸显示的时候做绘制操作(占用CPU)。
*/
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
Log.d("clock", "onVisibilityChanged >>> $visible")
//只在壁纸显示的做绘制操作,这很重要!
if (visible) {
surfaceHolder.lockCanvas()?.let { canvas ->
//将原点移动到画布中心
canvas.save()
canvas.translate((canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
//绘制文字
canvas.drawText("Hong Kong is part of China!", 0f, 0f, mPaint)
canvas.restore()
surfaceHolder.unlockCanvasAndPost(canvas)
}
}
}
}
}
/**
* Must be implemented to return a new instance of the wallpaper's engine.
* Note that multiple instances may be active at the same time, such as
* when the wallpaper is currently set as the active wallpaper and the user
* is in the wallpaper picker viewing a preview of it as well.
*
* 必须实现并返回一个壁纸引擎的新实例。
* 注意同一时间可能有多个实例在运行,比如当前壁纸正在运行时,用户在挑选壁纸页面浏览该壁纸的预览画面。
*/
public abstract Engine onCreateEngine();
/**
* The actual implementation of a wallpaper. A wallpaper service may
* have multiple instances running (for example as a real wallpaper
* and as a preview), each of which is represented by its own Engine
* instance. You must implement {@link WallpaperService#onCreateEngine()}
* to return your concrete Engine implementation.
*
* 壁纸的实际实现。一个壁纸服务可能有多个实例在运行(例如一个是真实的壁纸和一个处于预览的壁纸),
* 每个壁纸都只能由其相应的引擎实例来做实现。
* 你必须实现{@link WallpaperService#onCreateEngine()}并返回你创建的引擎的实例。
*/
public class Engine {
...省略代码
}
inner class MyEngine : Engine() {
override fun onCreate(surfaceHolder: SurfaceHolder?) {
super.onCreate(surfaceHolder)
Log.d("clock", "onCreate")
}
override fun onSurfaceCreated(holder: SurfaceHolder?) {
super.onSurfaceCreated(holder)
Log.d("clock", "onSurfaceCreated")
}
override fun onSurfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
super.onSurfaceChanged(holder, format, width, height)
Log.d("clock", "onSurfaceChanged")
}
/**
* Called to inform you of the wallpaper becoming visible or
* hidden. <em>It is very important that a wallpaper only use
* CPU while it is visible.</em>.
*
* 当壁纸显示或隐藏是会回调该方法。
* 很重要的一点是,要只在壁纸显示的时候做绘制操作(占用CPU)。
*/
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
Log.d("clock", "onVisibilityChanged >>> $visible")
}
override fun onSurfaceDestroyed(holder: SurfaceHolder?) {
super.onSurfaceDestroyed(holder)
Log.d("clock", "onSurfaceDestroyed")
}
override fun onDestroy() {
super.onDestroy()
Log.d("clock", "onDestroy")
}
}
绘制相关的API
/**
* Provides access to the surface in which this wallpaper is drawn.
* 提供对绘制壁纸时实际Surface(表面)的访问
*/
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
/**
* Start editing the pixels in the surface. The returned Canvas can be used
* to draw into the surface's bitmap. A null is returned if the surface has
* not been created or otherwise cannot be edited. You will usually need
* to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
* to find out when the Surface is available for use.
*
* 开始在Surface上编辑像素。这个返回的画布可以用来在Surface的位图上绘制。如果Surface还没创建或者不能被编辑会返回null。
* 一般情况下,你需要通过实现{@link Callback#surfaceCreated Callback.surfaceCreated}这个方法,
* 来得知surface什么时候可用。
*
* <p>The content of the Surface is never preserved between unlockCanvas() and
* lockCanvas(), for this reason, every pixel within the Surface area
* must be written. The only exception to this rule is when a dirty
* rectangle is specified, in which case, non-dirty pixels will be
* preserved.
*
* Surface的内容在unlockCanvas()和lockCanvas()之间是不会保存的,因此,Surface区域必须写入每个像素。
* 这个规则有个例外就是指定一个特殊的脏矩形区域,这种情况下,非脏区域的像素才会被保存。
*
* <p>If you call this repeatedly when the Surface is not ready (before
* {@link Callback#surfaceCreated Callback.surfaceCreated} or after
* {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls
* will be throttled to a slow rate in order to avoid consuming CPU.
*
* 如果你在Surface创建前或销毁后重复调用该方法,为了避免占用CPU,你的调用将被限制为慢速率。
*
* <p>If null is not returned, this function internally holds a lock until
* the corresponding {@link #unlockCanvasAndPost} call, preventing
* {@link SurfaceView} from creating, destroying, or modifying the surface
* while it is being drawn. This can be more convenient than accessing
* the Surface directly, as you do not need to do special synchronization
* with a drawing thread in {@link Callback#surfaceDestroyed
* Callback.surfaceDestroyed}.
*
* 如果返回值不为null,该方法内部持有锁,直到相应的{@link #unlockCanvasAndPost}方法被调用,并会防止Surface
* 在绘制时被创建、销毁或修改。这样比直接访问Surface更方便,因为你不需要在{@link Callback#surfaceDestroyed
* Callback.surfaceDestroyed}和绘制线程中做特殊的同步。
*
* @return Canvas Use to draw into the surface.
* 返回一个用来绘制到Surface上的画布
*/
public Canvas lockCanvas();
/**
* Finish editing pixels in the surface. After this call, the surface's
* current pixels will be shown on the screen, but its content is lost,
* in particular there is no guarantee that the content of the Surface
* will remain unchanged when lockCanvas() is called again.
*
* 完成Surface上像素的编辑。该方法调用完,Surface上的像素将会展示到屏幕上,但是它的内容会丢失,
* 尤其再次调用lockCanvas()时也不能保证它的内容不会变动。
*
* @see #lockCanvas()
*
* @param canvas The Canvas previously returned by lockCanvas().
* 参数需传入之前lockCanvas()返回的画布
*/
public void unlockCanvasAndPost(Canvas canvas);
源码地址
设置动态壁纸-TextClockWallpaperService
起源
填坑啦!填坑啦!
其实关于「设置动态壁纸的实操」我是弃坑了的,因为我单方面觉得只是壁纸相关API的使用,价值不大。但不久前有「掘友」留言说及这事,并表示期待更新「下篇」,于是我又单方面觉得价值还是有的,能帮一个是一个。
目录
以下是我列的本篇目录,将按顺序依次做解说
如何快速上手设置壁纸?
NOTE: 这里暂时不关心「为什么」,我们只按照既定的步骤,快速上手实现一个「Hong Kong is part of China!」静态壁纸
res -> xml
目录下新建一个壁纸描述文件,名字可自取(text_colck_wallpaper.xml),内容很简单继承
WallpaperService
在内部处理我们自己的绘制。(可以关注下onVisibilityChanged方法)AndroidManifest.xml
文件中增加壁纸服务的声明经过前面四步,我们成功的爱了一把国,效果如图:
相关API说明
壁纸描述文件
首先,这个描述文件是必须的,在声明服务的时候必须在meta-data上配置上。知道为什么吗?
description(对壁纸服务的描述)
settingsActivity(对此壁纸进行参数设置的Activity)
thumbnail(壁纸服务缩略图)
WallpaperInfo
的实例。(关于原理部分解析,我在拜读的文章中贴出了链接,大家可自行食用)壁纸服务的声明
其中必须要加的是:
android:permission="android.permission.BIND_WALLPAPER"
<action android:name="android.service.wallpaper.WallpaperService"/>
对!这两个也分别对应壁纸服务启动前的第一个检查和第二个检查
壁纸服务的实现
继承
WallpaperService
并复写抽象方法public abstract Engine onCreateEngine();
Engine
的关键生命周期,它们是从上到下依次执行的。我们重点关注onVisibilityChanged
onSurfaceDestroyed
即可。绘制相关的API
文字时钟动态壁纸实践
有了前面的铺垫,这部分就相对简单了。不过我们依旧先思考🤔下思路:
如何绘制文字时钟?「上篇」中我们已经有了「TextClockView」,我们直接把它当做一个
封装好的对象
,再扩展添加几个我们需要的方法即可使用。如何让壁纸动起来?跟「上篇」中一样,开一个定时器,每秒钟调用一次
TextClockView
的doInvalidate()
方法。扩展TextClockView
首先要能在外部初始化绘制时依赖的宽高
绘制方法中增加回调,用于将实际绘制调用放到壁纸服务中
处理壁纸服务的具体绘制实现
核心绘制操作
到这里就完成啦!撒花!撒花!(欢迎食用源码并实际体验~)
文末
个人能力有限,如有不正之处欢迎大家批评指出,我会虚心接受并第一时间修改,以不误导大家。
拜读的文章
我的其它文章