设置动态壁纸-TextClockWallpaperServicehtml
填坑啦!填坑啦!java
其实关于「设置动态壁纸的实操」我是弃坑了的,由于我单方面以为只是壁纸相关API的使用,价值不大。但不久前有「掘友」留言说及这事,并表示期待更新「下篇」,因而我又单方面以为价值仍是有的,能帮一个是一个。android
如下是我列的本篇目录,将按顺序依次作解说git
NOTE: 这里暂时不关心「为何」,咱们只按照既定的步骤,快速上手实现一个「Hong Kong is part of China!」静态壁纸github
res -> xml
目录下新建一个壁纸描述文件,名字可自取(text_colck_wallpaper.xml),内容很简单<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
复制代码
WallpaperService
在内部处理咱们本身的绘制。(能够关注下onVisibilityChanged方法)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)
}
}
}
}
}
复制代码
AndroidManifest.xml
文件中增长壁纸服务的声明<!--动态壁纸服务-->
<service
android:name=".view.TextClockWallpaperService"
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/text_clock_wallpaper" />
</service>
复制代码
btnSet.setOnClickListener {
val intent = Intent().apply {
action = WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER
putExtra(
WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
ComponentName(
context,
TextClockWallpaperService::class.java
)
)
}
startActivity(intent)
}
复制代码
通过前面四步,咱们成功的爱了一把国,效果如图:canvas
首先,这个描述文件是必须的,在声明服务的时候必须在meta-data上配置上。知道为何吗?bash
description(对壁纸服务的描述)
settingsActivity(对此壁纸进行参数设置的Activity)
thumbnail(壁纸服务缩略图)
//示例代码
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/description"
android:settingsActivity="me.erwa.xxx.SettingsActivity"
android:thumbnail="@mipmap/ic_launcher">
</wallpaper>
复制代码
WallpaperInfo
的实例。(关于原理部分解析,我在拜读的文章中贴出了连接,你们可自行食用)其中必需要加的是:app
android:permission="android.permission.BIND_WALLPAPER"
<action android:name="android.service.wallpaper.WallpaperService"/>
对!这两个也分别对应壁纸服务启动前的第一个检查和第二个检查ide
WallpaperService
并复写抽象方法public abstract Engine onCreateEngine();
/** * 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 {
...省略代码
}
复制代码
Engine
的关键生命周期,它们是从上到下依次执行的。咱们重点关注onVisibilityChanged
onSurfaceDestroyed
便可。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")
}
}
复制代码
/** * 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);
复制代码
有了前面的铺垫,这部分就相对简单了。不过咱们依旧先思考🤔下思路:布局
封装好的对象
,再扩展添加几个咱们须要的方法便可使用。TextClockView
的doInvalidate()
方法。/** * 初始化宽高,供动态壁纸使用 */
fun initWidthHeight(width: Float, height: Float) {
if (this.mWidth < 0) {
this.mWidth = width
this.mHeight = height
mHourR = mWidth * 0.143f
mMinuteR = mWidth * 0.35f
mSecondR = mWidth * 0.35f
}
}
复制代码
/** * 开始绘制 */
fun doInvalidate(block: (() -> Unit)? = null) {
this.mBlock = block
Calendar.getInstance().run {
...省略代码
mAnimator.addUpdateListener {
...省略代码
if (this@TextClockView.mBlock != null) {
this@TextClockView.mBlock?.invoke()
} else {
invalidate()
}
}
mAnimator.start()
}
}
复制代码
/** * 中止后续绘制,供动态壁纸使用 */
fun stopInvalidate() {
mAnimator.removeAllUpdateListeners()
}
复制代码
inner class MyEngine : Engine() {
private val mClockView = TextClockView(this@TextClockWallpaperService.baseContext)
private val mHandler = Handler()
private var mTimer: Timer? = null
...省略代码
}
复制代码
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
Log.d("clock", "onVisibilityChanged >>> $visible")
if (visible) {
startClock()
} else {
stopClock()
}
}
/** * 开始绘制 */
private fun startClock() {
if (mTimer != null) return
mTimer = timer(period = 1000) {
mHandler.post {
mClockView.doInvalidate {
if (mTimer != null && surfaceHolder != null) {
surfaceHolder.lockCanvas()?.let { canvas ->
mClockView.initWidthHeight(canvas.width.toFloat(), canvas.height.toFloat())
mClockView.draw(canvas)
surfaceHolder.unlockCanvasAndPost(canvas)
}
}
}
}
}
}
/** * 中止绘制 */
private fun stopClock() {
mTimer?.cancel()
mTimer = null
mClockView.stopInvalidate()
}
复制代码
到这里就完成啦!撒花!撒花!(欢迎食用源码并实际体验~)
我的能力有限,若有不正之处欢迎你们批评指出,我会虚心接受并第一时间修改,以不误导你们。