为佳明fenix 7开发表盘,随机背景图片切换
去年大水博主海淘买了一块Garmin智能手表,fenix 7。这款手表属于户外运动手表,配置MIP反射式屏幕,带太阳能充电。功能和续航都还可以,就是表的厚度戴在俺的小手腕上略显手腕更小。
昨天下载了ConnectIQ,即佳明的应用商店。翻找了一下表盘应用,并没有找到我想要的。
智能手表因为电池容量很小,所以WatchFace程序的运行效率就至关重要。商店的表盘适配了好多型号,各个手表支持的特性又不尽相同,开发者为了做适配,势必会执行很多分支判断。而俺就一个fenix,何不就这个型号开发一个表盘呢,这样就能保证无任何多余代码。
博主吐槽:现在的手机App真有必要做那么大吗,都TM先别加feature了,能把没用的玩意删了吗?真没必要使用一个库的小函数,而引入整个lib。大部分App全TM是缝合怪。
ConnectIQ Face It允许用户定制自己的表盘,但是背景只能使用一张。而且显示信息由于背景图的存在,导致看不清(实拍如下图NSFW)。我要做的是背景图15分钟换一张,并且当抬起手腕看手表时,背景图隐藏,突出文字显示。
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW
NSFW

Garmin为开发者提供了SDK,使用Monkey C
语言。俺也是第一次接触这个猴子C,对于有编程经验的码农可以直接上手,类似Java和JS的缝合怪。
api文档保持打开状态,随用随查。
以示例为基础
按照文档创建一个WatchFace项目。
最终效果如图

6.8/123.9kB代表WatchFace总可用内存123.9kB,已用6.8k。
准备背景图片
首先准备一批要显示的图片。由于俺的手表fenix 7分辨率是260x260,要把图片进行截取和缩放。不要在程序里处理缩放,能提前处理好的尽量不让手表来做。
把图片放到drawables目录,并编辑drawables.xml
<drawables>
<bitmap id="LauncherIcon" filename="launcher_icon.png" />
<bitmap id="b1" filename="1.png" />
<bitmap id="b2" filename="2.png" />
<bitmap id="b3" filename="3.png" />
<bitmap id="b4" filename="4.png" />
<bitmap id="b5" filename="5.png" />
<bitmap id="b6" filename="6.png" />
<bitmap id="b7" filename="7.png" />
<bitmap id="b8" filename="8.png" />
<bitmap id="b9" filename="9.png" />
<bitmap id="b10" filename="10.png" />
<bitmap id="b11" filename="11.png" />
<bitmap id="b12" filename="12.png" />
<bitmap id="b13" filename="13.png" />
<bitmap id="b14" filename="14.png" />
<bitmap id="b15" filename="15.png" />
<bitmap id="b16" filename="16.png" />
<bitmap id="b17" filename="17.png" />
<bitmap id="b18" filename="18.png" />
<bitmap id="b19" filename="19.png" />
<bitmap id="b20" filename="20.png" />
<bitmap id="b21" filename="21.png" />
<bitmap id="b22" filename="22.png" />
</drawables>
爷们准备了22张图片。
代码实现在WatchFaceView.mc文件中。
随机图片算法的选择
最开始选择最简单的实现方法,一行代码搞定
private function get_ramdom_bitmap_res_id() {
return bitmap_res_id[Math.rand() % bitmap_res_id.size()];
}
bitmap_res_id存放的是图片资源数组,从数组中随机选择一张。这种方法的缺点在于太过随机,有时候可能运气爆表,连续随机了同一张图片,相反,另一些图片有可能很长时间得不到临幸。
最后换用了类似音乐播放器的shuffle随机方式
private function get_ramdom_bitmap_res_id() {
// shuffle
if (bigmap_shuffle_res_id.size() == 0 ) {
Math.srand(Time.now().value());
for (var i = bitmap_res_id.size() - 1; i > 0; i--) {
var j = Math.floor(Math.rand() * 1.0 / 2147483647 * (i + 1)).toNumber();
if (i != j) {
var temp = bitmap_res_id[i];
bitmap_res_id[i] = bitmap_res_id[j];
bitmap_res_id[j] = temp;
}
}
bigmap_shuffle_res_id.addAll(bitmap_res_id);
}
// get index 0 element and delete
var bitmap_id = bigmap_shuffle_res_id[0];
bigmap_shuffle_res_id.remove(bitmap_id);
return bitmap_id;
}
显示图像和文字
private function drawRandomBitmap(dc as Dc) {
var targetDc = null;
if (offscreenBuffer != null) {
dc.clearClip();
//if we have an offscreen buffer that we are using to draw the background,
//set the draw context of that buffer as our target.
targetDc = offscreenBuffer.getDc();
} else {
targetDc = dc;
}
bg_bitmap = Application.loadResource(get_ramdom_bitmap_res_id());
targetDc.drawBitmap(0, 0, bg_bitmap);
dc.drawBitmap(0, 0, offscreenBuffer);
}
onUpdate,系统每1秒钟调用一次,在这里实现显示和切换
function onUpdate(dc as Dc) as Void {
var clockTime = System.getClockTime();
//每15分钟更新背景
if (clockTime.min % 15 == 0 && clockTime.sec == 0) {
drawRandomBitmap(dc);
}
// 如果退出睡眠(抬起手腕)
if (exitSleep == true) {
var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
var view = View.findDrawableById("TimeLabel") as Text;
view.setText(timeString);
// Call the parent onUpdate function to redraw the layout
View.onUpdate(dc);
}
else {
// 重新显示图像,直接使用buffer
dc.drawBitmap(0, 0, offscreenBuffer);
}
}
在模拟器上运行效果

目前我只显示了时间。我本来找了几个开源表盘(把layerout直接移植),让显示的信息丰富一些。但是这些表盘太难看,俺还是喜欢系统自带的那个。俺目前没有把系统表盘复现的想法,毕竟,我只看图,不说话。
系统表盘并不是用Monkey C实现的,而是系统级的实现。
大水博主喜欢果体大美妞,也喜欢自然。果和然可以兼得,大水博主果然有两把刷子。


编译发布
俺是自己用,不上传Connect IQ,上传估计也审核不过。直接传USB到GARMIN/Apps下。最终编译出来的RPG文件大小1.49M。
盆友,你有什么理由不为自己的手表开发表盘呢?
骚里骚气的大水博主撸着手表,划着滑板,去钓鱼。
