在mac上看到一款炫酷锁屏,所以按照自己的思路实现了其中一个效果,顺手码了个App,名为“时间轮盘”。“时间轮盘”包括动态壁纸和屏保功能。
下载地址

实现效果



准备

工欲善其事必先利其器,要实现功能首先需要了解相关技术栈。“时间轮盘”涉及到绘制,因此需要了解Canvas,Paint,Matrix相关Api。最终需要将绘制的图形展示到动态壁纸和屏保,因此需要分别继承WallpaperService和DreamService,并实现相关方法。

实现

1.绘制轮盘

实现程序绕不开绘制,因此我们先自定义一个View,重写onDraw()方法来实现轮盘的绘制。根据效果图分析,轮盘的环绕文字从内向外逐层显示,每一层显示一种具体时间字段。所以我们先实现一层(月份)绘制,接下的按照同样的方法进行绘制。

查阅文档,安卓关于文字绘制有以下方法

1
2
3
4
public void drawText (CharSequence text, int start, int end, float x,float y, Paint paint)
public void drawText (String text, float x, float y, Paint paint)
public void drawText (char[] text, int index, int count, float x, float y, Paint paint)
public void drawText (String text, int start, int end, float x, float y, Paint paint)

我们只需要使用到

1
public void drawText (String text, float x, float y, Paint paint)

首先我们现在原点进行绘制”一月”,那么我们如何将绘制内容移动到画布中央呢,这时候我们就需要用到Matrix,Matrix在图形绘制中起着重要的作用,通过Matrix我们可以实现图形的缩放,旋转,平移以及它们的组合效果。

先使用

1
public boolean postTranslate(float dx, float dy)

进行平移,根据效果图我们发现绘制距离中点还有一定距离,我们仅需再向右平移一段距离,最后通过调用

1
Canvas.setMatrix(Matrix matrix)

来应用Matrix,就可以愉快的绘制了。

接下来我们再绘制”二月”,和”一月”不同的是,”二月”相对于”一月”有一定的偏转角度,我们只要围绕画布中心点进行旋转就行了。

1
public boolean postRotate(float degrees, float px, float py)

旋转角度为360/12=30,同理接下来的月份就好办了。

将代码封装下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private String[] months = {
"壹", "贰", "仨", "肆", "伍", "陆",
"柒", "捌", "玖", "拾", "拾壹", "拾贰"
};

private void onDraw(Canvas canvas) {
drawText(canvas,40,months)
}

/**
*
* @param canvas
* @param radius 距中心距离
* @param strArray 轮盘文字数组
*/
private void drawText(Canvas canvas, float radius, String[] strArray) {
int width = canvas.getWidth();
int height = canvas.getHeight();
float addD = 360f / strArray.length;
float degree = 0f;
for (String str : strArray) {
//重置Matrix
matrix.reset();
//平移Matrix
matrix.postTranslate(width / 2, height / 2);
matrix.postTranslate(radius, 0);
//旋转Matrix
matrix.postRotate(degree, width / 2, height / 2);
//设置Matrix
canvas.setMatrix(matrix);
canvas.drawText(str, 0, 0, clockPaint);
degree += addD;
}
}

通过以上代码,我们便能够实现一个静态的时间轮盘了。

2.显示正确的时间

根据效果图我们知道,当前时间高亮显示,并且总是显示在右侧一行。我们需要将整个圆环进行旋转,一直转到当前时间。

首先我们通过调用Calendar的方法

1
2
3
4
5
6
7
public int get(int field)

Calendar.MONTH
Calendar.DATE
Calendar.HOUR_OF_DAY
Calendar.MINUTE
Calendar.SECOND

获取到月,日等时间字段,拿月示例,现在是四月,根据

1
Calendar.get(Calendar.MONTH)

我们获取到的值3,我们只需要逆时针旋转(360/12)*3就能够将四月旋转到正确的位置,并且将其设置高亮色,其他同理。


3.让我们的轮盘动起来

通过 (当前毫秒%1000/1000f) * 360 可以知道当前秒数的偏移量,在绘制的时候只要再加上这个偏移量,再通过一个定时器定时的执行重绘工作,便可以实现轮盘的转动了。

4.显示到壁纸

4.1添加壁纸权限

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

4.2实现壁纸服务

继承WallpaperService并实现onCreateEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
override fun onCreateEngine(): WallpaperService.Engine {
return LiveWallpaperEngine()
}

private inner class LiveWallpaperEngine : WallpaperService.Engine() {

override fun onSurfaceCreated(holder: SurfaceHolder) {
super.onSurfaceCreated(holder)
//获取画布
val canvas = holder!!.lockCanvas()
//绘制到画布
...
...
//显示到屏幕
holder!!.unlockCanvasAndPost(canvas)
}

override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
//为了省电,我们可以在壁纸不显示的时候,停止绘制线程
}

override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
super.onSurfaceChanged(holder, format, width, height)
//在屏幕旋转时调整画布大小
}

override fun onSurfaceDestroyed(holder: SurfaceHolder) {
super.onSurfaceDestroyed(holder)
//销毁绘制线程
}
}

4.3添加my_wallpaper.xml

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name" />

4.4添加服务到清单

1
2
3
4
5
6
7
8
9
10
11
<service
android:name=".service.LiveWallPaperService"
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>

4.5跳转设置壁纸

1
2
3
4
5
Intent intent = new Intent();
intent.setAction(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
new ComponentName(activity.getApplicationContext().getPackageName(), LiveWallPaperService.class.getCanonicalName()));
activity.startActivityForResult(intent, requestCode);

网页版轮盘

https://ghbhaha.github.io/DateTimewallpaper_Communication/editer.html