Android wallpaper is still fun at station B

The function of setting the system wallpaper does not actually have many scenes for the application layer App, but in the surrounding activities of some scenes, it is indeed a way to enhance the brand stickiness, just like the beautiful wallpaper of a character created in a certain activity. Figure, these can add a function to set the wallpaper.

Since the original Android, the system supports setting wallpapers in two ways, one is static wallpaper and the other is dynamic wallpaper.

static wallpaper

There is nothing to say about static wallpaper, and it is done with one line of code through the API provided by the system.

The simplest code is shown below.

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()
}
复制代码

In addition to setBitmap, the system also provides setResource, setStream, a total of three ways to set static wallpaper.

The three methods have the same goal, all set a Bitmap to the system API.

dynamic wallpaper

Live wallpapers are a bit interesting. Many mobile phone ROMs also have built-in live wallpapers. Don’t think these are new features. Since Android 1.5, this method has been supported. It’s just that there are fewer people doing it. Why, mainly because there are no particularly suitable scenes, and dynamic wallpapers consume more power than static wallpapers, so most of the time, we don’t use this method.

As a system service, when the system starts, whether it is a dynamic wallpaper or a static wallpaper, it will run in the background as a Service - WallpaperService, its Window type is TYPE_WALLPAPER, and WallpaperService provides a SurfaceHolder to expose to the outside world. The picture is rendered, which is the basic principle of setting the wallpaper.

To create a dynamic wallpaper, you need to inherit the system's WallpaperService and provide a WallpaperService.Engin for rendering. The following is a template code.

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()
        }
    }
}
复制代码

Then register the Service in the manifest.

<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>
复制代码

In addition, you need to apply for the appropriate permissions.

<uses-permission android:name="android.permission.SET_WALLPAPER" />
复制代码

Finally, add a description file in the xml folder, corresponding to the file in the resource tag above.

<?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" />
复制代码

Live wallpapers can only be set through the system's wallpaper preview interface.

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)
复制代码

So we can set a live wallpaper.

play with flowers

Since the provided SurfaceHolder is used for rendering, all of our scenes that can use the SurfaceHolder can be used to create dynamic wallpapers.

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

  • 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站里面新加了一个异想少女系列,你可以设置一个动态壁纸,同时还带交互,有点意思。

其实类似这样的交互,基本上都是通过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,但我觉得这东西的使用场景太有限了,而且特别卡,极端影响功耗,所以,要不要这么卷呢,你看着办吧。

Guess you like

Origin juejin.im/post/7135998448720936968