設置動態壁紙-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()
}
複製代碼
到這裏就完成啦!撒花!撒花!(歡迎食用源碼並實際體驗~)
我的能力有限,若有不正之處歡迎你們批評指出,我會虛心接受並第一時間修改,以不誤導你們。