设置体系壁纸这个功用,关于应用层App来说,场景其实并不多,但在一些场景的周边活动中,确也是一种提高品牌粘性的办法,就比如某个活动中创立的人物的壁纸美图,这些就能够新增一个设置壁纸的功用。

从原始的Android开端,体系就支撑设置两种办法的壁纸,一种是静态壁纸,另一种是动态壁纸。

静态壁纸

静态壁纸没什么好说的,经过体系供给的API一行代码就完事了。

最简略代码如下所示。

val wallpaperManager = WallpaperManager.getInstance(this)
try {
    val bitmap = ContextCompat.getDrawable(this, R.drawable.ic_launcher_background)?.toBitmap()
    wallpaperManager.setBitmap(bitmap)
} catch (e: Exception) {
    e.printStackTrace()
}

除了setBitmap之外,体系还供给了setResource、setStream,一共三种办法来设置静态壁纸。

三种办法异曲同工,都是设置一个Bitmap给体系API。

动态壁纸

动态壁纸就有点意思了,很多手机ROM也内置了一些动态壁纸,别以为这些是什么新功用,从Android 1.5开端,就现已支撑这种办法了。只不过做的人比较少,为啥呢,主要是没有什么特别适宜的场景,并且动态壁纸,会比静态壁纸愈加耗电,所以大部分时分,咱们都没用这种办法。

壁纸作为一个体系服务,在体系启动时,不管是动态壁纸仍是静态壁纸,都会以一个Service的形式运转在后台——WallpaperService,它的Window类型为TYPE_WALLPAPER,WallpaperService供给了一个SurfaceHolder来暴露给外界来对画面进行渲染,这便是设置壁纸的基本原理。

创立一个动态壁纸,需求继承体系的WallpaperService,并供给一个WallpaperService.Engin来进行渲染,下面这个便是一个模板代码。

class MyWallpaperService : WallpaperService() {
    override fun onCreateEngine(): Engine = WallpaperEngine()
    inner class WallpaperEngine : WallpaperService.Engine() {
        lateinit var mediaPlayer: MediaPlayer
        override fun onSurfaceCreated(holder: SurfaceHolder?) {
            super.onSurfaceCreated(holder)
        }
        override fun onCommand(action: String?, x: Int, y: Int, z: Int, extras: Bundle?, resultRequested: Boolean): Bundle {
            try {
                Log.d("xys", "onCommand: $action----$x---$y---$z")
                if ("android.wallpaper.tap" == action) {
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return super.onCommand(action, x, y, z, extras, resultRequested)
        }
        override fun onVisibilityChanged(visible: Boolean) {
            if (visible) {
            } else {
            }
        }
        override fun onDestroy() {
            super.onDestroy()
        }
    }
}

然后在manifest中注册这个Service。

<service
    android:name=".MyWallpaperService"
    android:exported="true"
    android:label="Wallpaper"
    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/my_wallpaper" />
</service>

别的,还需求申请相应的权限。

<uses-permission android:name="android.permission.SET_WALLPAPER" />

最终,在xml文件夹中新增一个描绘文件,对应上面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="@mipmap/ic_launcher" />

动态壁纸只能经过体系的壁纸预览界面来进行设置。

val localIntent = Intent()
localIntent.action = WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER
localIntent.putExtra(
    WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
    ComponentName(applicationContext.packageName, MyWallpaperService::class.java.name))
startActivity(localIntent)

这样咱们就能够设置一个动态壁纸了。

玩点花

既然是运用供给的SurfaceHolder来进行渲染,那么咱们一切能够运用到SurfaceHolder的场景,都能够来进行动态壁纸的创立了。

一般来说,有三种比较常见的运用场景。

  • MediaPlayer
  • Camera
  • SurfaceView

这三种也是SurfaceHolder的常用运用场景。

首要来看下MediaPlayer,这是最简略的办法,能够设置一个视频,在桌面上循环播放。

inner class WallpaperEngine : WallpaperService.Engine() {
    lateinit var mediaPlayer: MediaPlayer
    override fun onSurfaceCreated(holder: SurfaceHolder?) {
        super.onSurfaceCreated(holder)
        mediaPlayer = MediaPlayer.create(applicationContext, R.raw.testwallpaper).also {
            it.setSurface(holder!!.surface)
            it.isLooping = true
        }
    }
    override fun onVisibilityChanged(visible: Boolean) {
        if (visible) {
            mediaPlayer.start()
        } else {
            mediaPlayer.pause()
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        if (mediaPlayer.isPlaying) {
            mediaPlayer.stop()
        }
        mediaPlayer.release()
    }
}

接下来,再来看下运用Camera来刷新Surface的。

inner class WallpaperEngine : WallpaperService.Engine() {
    lateinit var camera: Camera
    override fun onVisibilityChanged(visible: Boolean) {
        if (visible) {
            startPreview()
        } else {
            stopPreview()
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        stopPreview()
    }
    private fun startPreview() {
        camera = Camera.open()
        camera.setDisplayOrientation(90)
        try {
            camera.setPreviewDisplay(surfaceHolder)
            camera.startPreview()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
    private fun stopPreview() {
        try {
            camera.stopPreview()
            camera.release()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

一起需求添加下Camera的权限。

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

因为这儿偷闲,没有运用最新的CameraAPI,也没有动态申请权限,所以你需求自己手动去授权。

最终一种,经过Surface来进行自绘渲染。

val holder = surfaceHolder
var canvas: Canvas? = null
try {
    canvas = holder.lockCanvas()
    if (canvas != null) {
    		canvas.save()
        // Draw Something
    }
} finally {
    if (canvas != null) holder.unlockCanvasAndPost(canvas)
}

这儿就能够彻底运用Canvas的API来进行制作了。

这儿有一个比较杂乱的制作Demo,能够给我们参阅。

www.developer.com/design/buil…

有意思的办法

尽管WallpaperService是一个体系服务,但它也供给了一些比较有用的回调函数来协助咱们做一些有意思的东西。

onOffsetsChanged

当用户在手机桌面滑动时,有的壁纸图片会跟着左右移动,这个功用便是经过这个回调来实现的,在手势滑动的每一帧都会回调这个办法。

xOffset:x轴滑动的百分比

yOffset:y轴滑动百分比

xOffsetStep:x轴桌面Page数进展

yOffsetStep:y轴桌面Page数进展

xPixelOffset:x轴像素偏移量

经过这个函数,就能够拿到手势的移动惯量,然后对图片做出一些修改。

onTouchEvent、onCommand

这两个办法,都能够获取用户的点击行为,经过判别点击类型,就能够针对用户的特别点击行为来做一些逻辑处理,例如点击某些特定的地方时,唤起App,或者翻开某个界面等等。

class MyWallpaperService : WallpaperService() {
  override fun onCreateEngine(): Engine = WallpaperEngine()
  private inner class WallpaperEngine : WallpaperService.Engine() {
    override fun onTouchEvent(event: MotionEvent?) {
      // on finder press events
      if (event?.action == MotionEvent.ACTION_DOWN) {
        // get the canvas from the Engine or leave
        val canvas = surfaceHolder?.lockCanvas() ?: return
        // TODO
        // update the surface
        surfaceHolder.unlockCanvasAndPost(canvas)
      }
    }
  }
}

B站怎么玩的呢

不得不说,B站在这方面玩的是真的花,最近B站里边新加了一个异想少女系列,你能够设置一个动态壁纸,一起还带交互,有点意思。

Android壁纸还是B站玩得花

其实类似这样的交互,基本上都是经过OpenGL或者是RenderScript来实现的,经过GLSurfaceView来进行渲染,然后实现了一些杂乱的交互,下面这些比如,便是一些实践。

github.com/PavelDoGrea…

github.com/jinkg/live-…

www.cnblogs.com/YFEYI/categ…

code.tutsplus.com/tutorials/c…

但是B站的这个效果,明显比上面的方案愈加老练和完整,所以,经过调研能够发现,它们运用的是Live2D的方案。

www.live2d.com/

动态壁纸的Demo如下。

github.com/Live2D/Cubi…

这个东西是小日子的一个SDK,专业做2D可交互纸片人,这个东西现已出来很久了,前端之前用它来做网页的看板娘,现在客户端又拿来做动态壁纸,风水轮番换啊,想要运用的,能够参阅它们官方的Demo。

但是官方的动态壁纸Demo在客户端是有Bug的,会存在各种闪的问题,因为我自身不明白OpenGL,所以也无法解决,经过回退Commit,发现能够直接运用这个CommitID : Merge pull request #2 from Live2D/create-new-function ,就没有闪的问题。

a9040ddbf99d9a130495e4a6190592068f2f7a77

好了,B站YYDS,但我觉得这东西的运用场景太有限了,并且特别卡,极点影响功耗,所以,要不要这么卷呢,你看着办吧。