按要求把常亮再砍一档。常亮下现在不画:整条日期带底色、整个进度环、
四个数据位与电池;保留的时间、冒号、日期由 55% 压暗到 38%。
实测(fenix847mm,取表盘内区):
点亮像素数 平均亮度(功耗代理)
普通模式 27.6% 14.1%
常亮(改前) 10.4% 3.0%
常亮(改后) 9.7% 2.5%
两个口径的差别值得记一笔:「点亮像素数」把每个抗锯齿边缘像素都算成亮的,
对大号数字表盘天然不友好 —— 时间数字压到 38% 后白色仍有 ~97 亮度,照样计入。
AMOLED 的实际功耗更接近各子像素亮度之和,也就是「平均亮度」那一列。
两个口径现在都留了余量,38% 下时间和日期在模拟器里依然清晰可读。
LICENSE、UserProfile 的 try 保护、CHANGELOG 与版本号 1.1.0 已在上一个提交
22340b0 完成,本次只动常亮相关部分与对应文档。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
386 lines
24 KiB
Markdown
386 lines
24 KiB
Markdown
# Fenix 8 V3 — Garmin 数据表盘
|
||
|
||
按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED 与圆屏 MIP 共 **55 款**设备(Fenix 7/8 全系含 Pro、Epix 2、Enduro 3、Venu 2/3、FR 系列、Approach S50/S70、Descent、D2、Instinct 3 AMOLED 等)。
|
||
|
||
- **SDK**:Connect IQ SDK 9.1.0(`monkeyc` 9.1.0,`minSdkVersion="4.2.0"`)
|
||
- **语言**:Monkey C;界面文案含英文与简体中文
|
||
- **类型**:`watchface`
|
||
- **应用 ID**:`f8a3c1b2c3d44e5f8a9b0c1d2e3f4a5b`
|
||
|
||
设计稿本体(7 张 SVG + tokens + 逆向脚本)在 `design/`,是本仓库所有坐标与配色的唯一来源。
|
||
|
||
---
|
||
|
||
## 1. 目录结构
|
||
|
||
```
|
||
fenix8v3-watchface/
|
||
├── manifest.xml # 应用清单(ID / 支持设备 / 语言)
|
||
├── monkey.jungle # 构建配置
|
||
├── design/ # ⭐ 设计稿原件(唯一事实来源)
|
||
│ ├── watchface-0N-*.svg # 7 套主题的矢量还原稿
|
||
│ ├── watchface-tokens.json # 配色 token
|
||
│ └── rebuild.py # 原作者从位图逆向出 SVG 的脚本(仅存档,不参与构建)
|
||
├── tools/
|
||
│ ├── gen_themes.py # design/*.svg -> source/Themes.mc
|
||
│ ├── gen_fields.py # 字段表 -> FieldTable.mc + settings.xml + 两种语言文案
|
||
│ ├── gen_devices.py # 扫描 SDK -> manifest 机型列表 + monkey.jungle
|
||
│ ├── gen_icons.py # design/*.svg -> 各尺寸图标位图 + 启动图标
|
||
│ └── gen_preview_svg.py # source/Themes.mc -> preview-themes.svg + preview.html
|
||
├── source/
|
||
│ ├── Fenix8V3App.mc # 应用入口(12 行壳子)
|
||
│ ├── Fenix8V3View.mc # 绘制顺序:进度环 / 各元素 / 常亮模式
|
||
│ ├── Layout.mc # 500 画布常量、缩放、字号选择、文字绘制
|
||
│ ├── Icons.mc # 图标:位图三件套 + 其余图元
|
||
│ ├── Fields.mc # 125 项数据字段的取值与格式化
|
||
│ ├── Solar.mc # 日出/晨昏蒙影/黄金蓝调时刻的太阳高度角计算
|
||
│ ├── Owm.mc # OpenWeatherMap 缓存读取
|
||
│ ├── Comp.mc # 系统 / 第三方 Complication 桥接
|
||
│ ├── Fenix8V3Background.mc # 后台服务:拉取 OWM
|
||
│ ├── Settings.mc # 所有设置项的读取与默认值
|
||
│ ├── FieldTable.mc # ⚠️ 自动生成:字段 id 表
|
||
│ └── Themes.mc # ⚠️ 自动生成:七套主题配色
|
||
├── resources/ # 英文文案 + 设置项定义(部分自动生成)
|
||
├── resources-chn/ # 简体中文文案
|
||
├── resources-icons-<宽度>/ # ⚠️ 自动生成:按屏幕尺寸光栅化的图标
|
||
├── resources-launcher-<尺寸>/ # ⚠️ 自动生成:各尺寸启动图标
|
||
└── bin/ # 编译产物(已 gitignore)
|
||
```
|
||
|
||
重新生成全部派生文件:
|
||
|
||
```bash
|
||
python3 tools/gen_devices.py \
|
||
&& python3 tools/gen_themes.py && python3 tools/gen_fields.py \
|
||
&& python3 tools/gen_icons.py && python3 tools/gen_preview_svg.py
|
||
```
|
||
|
||
`gen_devices.py` 要先跑 —— 它决定了需要哪些图标尺寸,`gen_icons.py` 的
|
||
`WIDTHS` / `LAUNCHER` 两张表必须跟它对齐,少一档就会有机型编译不过。
|
||
|
||
三份渲染实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。字段表、设置项、两种语言的文案同样只有 `tools/gen_fields.py` 里那一张表。
|
||
|
||
---
|
||
|
||
## 1.5 代码结构
|
||
|
||
一句话:**位置归 `Layout`,内容归 `Fields`,配色归 `Themes`,设置归 `Settings`,`Fenix8V3View` 只剩画的顺序。**
|
||
|
||
| 模块 | 负责 | 不负责 |
|
||
|------|------|--------|
|
||
| `Layout` | 500 基准画布的全部坐标常量、`px()` 缩放、极坐标换算、字号选择与自动缩字、渐变文字的分带绘制 | 具体画什么 |
|
||
| `Icons` | 三个位图图标的加载与染色、电池、蓝牙、其余数据项的图元 | 图标该出现在哪个数据位(由字段 id 决定,但查表在这里) |
|
||
| `Fields` | 75 项字段的取值、单位换算、本地化文案(星期/月份/标签) | 排版 |
|
||
| `Settings` | 每个设置项的读取与默认值,包含空值回退 | 设置项的 UI 定义(在 `settings.xml`) |
|
||
| `Fenix8V3View` | `onUpdate` 的绘制顺序、进度环、常亮模式 | 以上任何一项的细节 |
|
||
|
||
`Fields.value()` 按分类拆成 7 个函数(`dateTime` / `activity` / `body` / `system` / `environment` / `weather` / `custom`),每个在不认识 id 时返回 `null`,`value()` 依次尝试。id 是交错的(1、2、3 是活动,8、11、12 是身体,14 又是活动),所以按区间派发行不通。
|
||
|
||
`Fields.Ctx` 每次绘制构造一个,把 `ActivityMonitor` / `Weather` / `UserProfile` / `SensorHistory` 的查询**懒加载并缓存**——四个数据位可能都要天气,但只查一次;没有数据位用到 `UserProfile` 时就完全不查。
|
||
|
||
|
||
## 2. 布局(500 基准,取自设计稿 SVG 路径的墨迹包围盒)
|
||
|
||
| 元素 | 位置 | 说明 |
|
||
|------|------|------|
|
||
| 进度环 | 内半径 `231.09`,外半径 `249.10` | 见 §3 |
|
||
| 电量图标 | `x=192.70`,`cy=53.20`,`26.20×17.81` | 圆角外框 + 按电量填充 + 右侧触点 |
|
||
| 电量数值 | 左对齐 `x=232.00` | `NN%` |
|
||
| 上方双数据 | 图标 `cy=90`,数值 `cy=131.06`;左 `x=157.00`,右 `x=343.70` | 居中对齐 |
|
||
| 时间 | `cy=221.10`;小时右对齐 `x=211.40`,分钟左对齐 `x=276.02` | 竖向渐变,见 §4 |
|
||
| 冒号 | `x=237.88`,`24.24×21.30`,`y=183.68 / 234.57` | 两块矩形 |
|
||
| 日期带 | `y=286.30`,高 `44.19`,文字 `cy=308.10` | 蓝牙 `cx=36.88`、星期 `cx=178.05`、月日 `cx=298.28`、AM/PM 右对齐 `465.89` |
|
||
| 底部数值 | `cy=366.76`;左格右对齐 `234.30`,右格左对齐 `267.40` | 过宽时自动降一档字号 |
|
||
| 底部标签 | `cy=410.88`,同上对齐 | 跟随所选数据(`DST` / `STEP` / `距离` …) |
|
||
|
||
字号按设计稿的**字面高度(cap height)**选取:日期带与电量 `25.34`、上方数据与底部行 `31.50`、时间 `98.70`。
|
||
|
||
---
|
||
|
||
## 3. 进度环
|
||
|
||
刻度是**等角宽的环形扇区**(`fillPolygon` 四点),不是径向线段。
|
||
|
||
- **顶弧**:21 根,角度 `6.5 + 4.0k`(k=0..20),角宽 `3.108°`
|
||
- **底弧**:21 根,角度 `6.005 + 3.0k`,角宽 `2.086°`
|
||
- **四象限**:右上 `+a`、左上 `-a`、左下 `180+a`、右下 `180-a`(角度为自 12 点起顺时针)
|
||
- **锚点圆点**:半径 `9.00`,位于圆心距 `240.10`、角度 `0 / 93.15 / 180 / 266.85`
|
||
|
||
**点亮方向**(与设计稿一致):两条顶弧从 9 点 / 3 点一侧朝 12 点填充;两条底弧从 6 点朝两侧填充。
|
||
|
||
每根刻度的颜色是设计稿里**逐根取样**的实测值(`Themes.RINGTOP[21]` / `RINGBOTTOM[21]`),不是插值近似。
|
||
|
||
> **已知近似**:设计稿中底弧最多只点亮 14/21 根,因此 `RINGBOTTOM` 的第 15–21 项无法实测,由 `gen_themes.py` 对最后 8 根做最小二乘线性外推得到。只有当指标超过约 67% 时才会显示这几根。
|
||
|
||
### 指标进度换算
|
||
|
||
| idx | 指标 | 分母 |
|
||
|-----|------|------|
|
||
| 0 | 步数 | `stepGoal` |
|
||
| 1 | 卡路里 | 固定 2000 kcal |
|
||
| 2 | 距离 | 10 km |
|
||
| 3 | 楼层 | `floorsClimbedGoal`(缺省 10) |
|
||
| 4 | 动动条 | `MOVE_BAR_LEVEL_MAX`(=5) |
|
||
|
||
---
|
||
|
||
## 4. 时间数字的竖向渐变
|
||
|
||
设计稿的小时 / 分钟数字带竖向多段渐变(如 Ember 分钟由 `#FEAA00` 渐变到 `#FF2600`)。Monkey C 无法给文字直接填渐变,实现方式是**同一串数字画 11 遍,每遍用 `dc.setClip` 限制在一条横向色带内**,色带颜色取自 `Themes.HOURS` / `Themes.MINUTES`(各 11 个停止点,实测自设计稿的 `linearGradient`)。首尾两条色带向外延伸,保证字形不会有未绘制的缝隙。
|
||
|
||
---
|
||
|
||
## 5. 字号选择
|
||
|
||
Connect IQ 只暴露 `getFontAscent` / `getFontDescent` / `getFontHeight`,**没有字面高度接口**,而 ascent 里含有数字和大写字母用不到的变音符空间。因此代码用两个实测比例把 ascent 折算成 cap height:
|
||
|
||
```
|
||
文本字体(Roboto 系) cap ≈ 0.76 × ascent
|
||
数字字体(Bionic 系) cap ≈ 0.71 × ascent
|
||
```
|
||
|
||
比例是在模拟器里渲染后、从截图上量出墨迹得到的(fenix847mm:`FONT_XTINY` ascent 29 → 大写高 22px;`FONT_NUMBER_MILD` ascent 82 → 数字高 58px)。`pickFont` 取折算后**最接近**目标 cap height 的字体,而不是"最大且不超过"——后者会在设计尺寸落在两档字体之间时白白小一号。
|
||
|
||
各机型内置字体不完全相同(Roboto-Regular / RobotoCondensed-Bold / Yantramanav / 各机型位图字体),比例会有几个百分点误差。设计稿使用的是更粗更方的第三方字体,不随包分发,字形差异无法消除。
|
||
|
||
---
|
||
|
||
## 6. 七套主题
|
||
|
||
由 `tools/gen_themes.py` 从 `design/watchface-0N-*.svg` 生成。每套包含:顶/底弧各 21 根刻度色、未点亮刻度色、锚点色、日期带底/字色、冒号色、小时/分钟各 11 段渐变、强调色(图标与 DST/STEP 标签)、主文字色(各项数值)。
|
||
|
||
| # | 主题 | 主色调 | 强调色 | 日期带 |
|
||
|---|------|--------|--------|--------|
|
||
| 1 | Ember | 黄 → 红 | `#FEAA05` | 橙底黑字 |
|
||
| 2 | Aurora | 黄绿 → 青 | `#ADABAE` | 深灰底白字 |
|
||
| 3 | Brass | 金铜 | `#B57E4B` | 深棕底白字 |
|
||
| 4 | Voltage | 蓝 → 黄 | `#ADABAE` | 蓝底黑字 |
|
||
| 5 | Reef | 橙 → 青 | `#FEAA05` | 青底黑字 |
|
||
| 6 | Acid | 黄绿(近单色) | `#C6FA03` | 深灰底黑字 |
|
||
| 7 | Kelp | 橙 → 绿 | `#34C294` | 深灰底黑字 |
|
||
|
||
改配色:编辑 / 替换 `design/` 里的 SVG,然后
|
||
|
||
```bash
|
||
python3 tools/gen_themes.py && python3 tools/gen_preview_svg.py
|
||
```
|
||
|
||
新增主题需同步在 `settings.xml` 的 `Theme` 列表与 `strings.xml`(含 `resources-chn/`)中登记。
|
||
|
||
---
|
||
|
||
## 7. 可配置设置与数据字段
|
||
|
||
19 项设置,在 Garmin Connect 手机 App 的「表盘设置」里改(定义在 `resources/settings/settings.xml`,默认值在 `properties.xml`,两者由 `tools/gen_fields.py` 生成)。
|
||
|
||
| 设置 | 键 | 默认 |
|
||
|------|----|------|
|
||
| 主题 | `Theme` | 1 (Ember) |
|
||
| 四段进度环指标 | `RingTR/TL/BR/BL` | 步数 / 卡路里 / 动动条 / 楼层 |
|
||
| 四个数据位 | `LeftTop` `RightTop` `BottomLeft` `BottomRight` | 日出 / 高低温 / 距离 / 步数 |
|
||
| 温度单位 | `TempUnit` | 跟随手表 |
|
||
| 第二时区 1 / 2 偏移(分钟) | `AltOffset1` `AltOffset2` | 0 |
|
||
| 自定义文字 1 / 2 / 3 | `Custom1/2/3` | 空 |
|
||
| 显示顶行 / 日期带 / 底行 | `ShowTop` `ShowBand` `ShowBottom` | true |
|
||
|
||
进度环指标:步数、卡路里、距离、楼层、动动条、活动分钟(对周目标)、身体电量。
|
||
|
||
### 数据字段
|
||
|
||
四个数据位共用一张 **156 项** 的字段表,编号沿用 [watchface.io/docs/datafields](https://watchface.io/docs/datafields),两边可以直接对照。底部两格的小标签跟随所选字段自动变化,不需要单独配置。
|
||
|
||
| 分类 | 字段 |
|
||
|------|------|
|
||
| 日期时间 | 秒(前导零两种)、时间、UTC 时间、第二时区 1/2、星期月日、星期日、月日、日、星期(简写/全称)、月份(名称/数字)、上午下午、周数(ISO / 日历) |
|
||
| 活动 | 步数、步数取整、剩余步数、卡路里、活动卡路里、活动分钟(总/中强度/高强度/本周)、距离、上楼层数、下楼层数、爬升高度、推行距离、推行次数、**本周 / 近 7 天 / 近 28 天累计**(步数/卡路里/距离/楼层/活动分钟) |
|
||
| 身体 | 心率、静息心率、压力指数、血氧、呼吸频率、身体电量、活动条(正/反向)、体重、BMI、恢复时间、最大摄氧量(跑/骑) |
|
||
| 系统 | 电量、剩余天数、电量+天数、闹钟、通知、勿扰、蓝牙、太阳能强度 |
|
||
| 环境 | 海拔、海平面气压、环境气压、日出、日落、下一太阳事件、距太阳事件、月龄、月球照明、民用/航海/天文晨昏蒙影(各 2 项)、黄金时刻(晨/昏)、蓝调时刻(晨/昏) |
|
||
| 天气 | 气温、最高/最低、体感、最高、最低、湿度、降水概率、风速、风向角、风向、更新时间、海平面气压、紫外线、能见度、露点 |
|
||
| OpenWeatherMap | 气温、体感、最高/最低、天气描述(简/详)、湿度、气压、能见度、风速、风向角、风向、阵风、1h 降雨、1h 降雪、云量、降水概率、露点、明日与后日气温及高低温、城市名、更新时间、**空气质量(AQI 等级 + 描述 + PM2.5 + PM10 + CO)**(共 30 项) |
|
||
| 系统 Complication | 下一个日程、训练状态、完赛预测(5k/10k/半马/全马)、配速预测(同上四项)、最近高尔夫得分、本周跑步距离、本周骑行距离、第三方 App 1–5 |
|
||
| 自定义 | 自定义文字 1 / 2 / 3、自定义时间格式、日期格式 1 / 2、事件倒计时 |
|
||
|
||
加字段:在 `tools/gen_fields.py` 的 `FIELDS` 表里加一行,跑一遍生成器,再到 `source/Fields.mc` 的 `value()` 里补一个分支。设置项、两种语言的文案、表盘标签会自动跟上。
|
||
|
||
### 系统 Complication
|
||
|
||
`Toybox.Complications`(CIQ 4.2+,17 款设备全都有)让表盘可以订阅系统和**其它 App 发布**的复杂功能。这条路解锁了一批原本以为做不到的字段:
|
||
|
||
- **日历事件** —— CIQ 确实没有日历 API,但系统把「下一个日程」作为 complication 发布了出来
|
||
- **训练状态、完赛/配速预测、最近高尔夫得分** —— `ActivityMonitor.Info` 和 `UserProfile` 上都没有,这里有
|
||
- **本周跑步 / 骑行距离** —— 这是**按运动分类**的,`ActivityMonitor` 的按天历史做不出来
|
||
- **第三方 App 1–5** —— CGM、补水、行情这类 App 发布的复杂功能,按序号取
|
||
|
||
需要 `ComplicationSubscriber` 权限。第三方 App 那五个槽位按「有值且有短标签」过滤后按序号取,避免序号随手表上装了什么 App 而漂移。
|
||
|
||
### 自定义格式与倒计时
|
||
|
||
时间/日期格式串用单字母占位符(设置项能填的长度有限,这样中英文都好写):
|
||
|
||
| 占位符 | 含义 | 占位符 | 含义 |
|
||
|--------|------|--------|------|
|
||
| `h` / `H` | 小时(跟随 12/24 制)/ 补零 | `D` | 日 |
|
||
| `m` / `s` | 分钟 / 秒(补零) | `M` / `N` | 月(数字 / 名称) |
|
||
| `a` | 上午/下午(24 小时制下为空) | `Y` / `y` | 四位年 / 两位年 |
|
||
| | | `w` | 星期 |
|
||
|
||
其它字符原样输出,所以 `D/M` → `10/9`,`w N D` → `周四 9月 10`。
|
||
|
||
事件倒计时填 `YYYY-MM-DD`,显示还剩几天。
|
||
|
||
### 秒针与后台天气
|
||
|
||
**秒**(850/869)通过 `onPartialUpdate` 每秒重绘:只有当某个数据位真的选了秒时才请求这个回调,并且只在该格的裁剪区内重绘,不动整个表盘。常亮的 AMOLED 机型不参与——那时数据位本来就不显示。
|
||
|
||
**OpenWeatherMap** 需要用户在设置里填自己的 API key(免费档即可)。没填就完全不联网,25 个 OWM 字段都显示 `--`。填了之后由后台服务每 30 分钟拉一次(当前天气 + `cnt=16` 的短预报,够算出明日/后日高低温),结果压平成一个小字典存进 `Application.Storage`,表盘只读不联网。
|
||
|
||
位置来自天气观测点或最近一次 GPS 定位,并缓存在 Storage 里——两个来源都会间歇性为空,缓存让日出和晨昏蒙影不会一会儿有一会儿没有。
|
||
|
||
### ⚠️ 仍然没有实现的,以及原因
|
||
|
||
| 没做的 | 原因 |
|
||
|--------|------|
|
||
| 按运动细分的多数汇总(游泳、自定义运动 1/2 的距离/时长/次数,约 60 项) | `ActivityMonitor.getHistory()` 只有按天总计,没有按运动分类的历史。**跑步和骑行的本周距离例外**——系统把这两项作为 complication 发布了,已经做了;游泳和自定义运动没有对应的 complication 类型 |
|
||
| 日历事件、计时器 | 日历已通过 Complication 做了。**计时器**仍然做不了:表盘拿不到触摸输入,没法启停 |
|
||
| StormGlass.io 潮汐(5 项) | 需要额外的付费 API key,且与本表盘的定位无关 |
|
||
| 第三方 App 复杂功能(CGM、Hydration、QuoteGlance 等 12 项) | 需要用户装了对应的 App,并通过 Complications API 订阅 |
|
||
| 睡眠得分 | `ActivityMonitor.Info` 上没有,也没有对应的 complication 类型(SDK 9.1.0 实测)。训练状态与完赛/配速预测已改由 Complication 提供 |
|
||
| 高尔夫成绩 | 无对应 API |
|
||
| 标记类字段(Marker,约 35 项) | 那是给刻度型表盘做环形标记用的,本表盘的进度环用的是自己的指标体系 |
|
||
| OWM 的 UVI | 要 One Call 3.0 订阅。**AQI 已经做了**——空气质量走 `/data/2.5/air_pollution`,那个接口在免费档就开放。露点用 Magnus 公式从温湿度算出 |
|
||
|
||
---
|
||
|
||
## 8. 数据来源与权限
|
||
|
||
| 元素 | API |
|
||
|------|-----|
|
||
| 步数 / 卡路里 / 距离 / 楼层 / 动动条 / 活动分钟 / 呼吸 / 压力 / 恢复时间 | `ActivityMonitor.getInfo()` |
|
||
| 心率 | `Activity.getActivityInfo().currentHeartRate`,回退 `ActivityMonitor.getHeartRateHistory()` |
|
||
| 血氧 / 身体电量 | `SensorHistory`(需 `SensorHistory` 权限) |
|
||
| 静息心率 / 体重 / 身高 / 最大摄氧量 | `UserProfile.getProfile()`(需 `UserProfile` 权限) |
|
||
| 海拔 / 环境气压 / 海平面气压 | `Activity.getActivityInfo()` |
|
||
| 日出 / 日落 | `Weather.getSunrise/getSunset(location, time)`,位置取 `CurrentConditions.observationLocationPosition`,回退 `Activity.currentLocation` |
|
||
| 气温 / 体感 / 湿度 / 降水 / 风 | `Weather.getCurrentConditions()` |
|
||
| 最高 / 最低气温 | `Weather.getDailyForecast()[0]` |
|
||
| 电量 / 剩余天数 / 太阳能 | `System.getSystemStats()`(`battery` 是 0–100 的百分比,不是 0–1) |
|
||
| 闹钟 / 通知 / 勿扰 / 蓝牙 / 单位制 | `System.getDeviceSettings()` |
|
||
| 月龄 / 月球照明 | 自算(儒略日 + 朔望月 29.530588853 天) |
|
||
| 周数 | 自算(ISO 8601 与日历周两种) |
|
||
|
||
### 权限
|
||
|
||
`manifest.xml` 声明五项:`UserProfile`(静息心率、体重、VO2max)、`SensorHistory`(血氧、身体电量)、`Communications` 与 `Background`(OpenWeatherMap)、`ComplicationSubscriber`(日历、训练状态、第三方 App)。用户安装时会看到授权提示。
|
||
|
||
> `Sensor` 权限**不能**用于 `type="watchface"`(除非同时申请 `Background`),编译器会直接拒绝。所以海拔与气压走 `Activity.Info` 而不是 `Sensor.getInfo()`。
|
||
|
||
单位与制式跟随手表:`is24Hour`、`distanceUnits`、`temperatureUnits`(温度可用 `TempUnit` 覆盖)。天气与日出需手表已配对并同步过数据,否则显示 `--`。
|
||
|
||
---
|
||
|
||
## 8.2 常亮显示(AOD)
|
||
|
||
AMOLED 机型在常亮模式下必须压低点亮像素,否则既费电又有烧屏风险,Garmin 上架审核也会卡。`onEnterSleep` / `onExitSleep` 切换到 `drawAmbient()`:
|
||
|
||
- 不画日期带底色(整条亮橙色横幅是最费电的元素)
|
||
- 进度环只画**点亮的**刻度,且整体压暗到 45%
|
||
- 时间与日期压暗到 55%,其余数据位全部隐藏
|
||
- 整个画面按分钟在 ±4px 内游走,避免固定像素长期点亮
|
||
|
||
只在 `DeviceSettings.requiresBurnInProtection` 为真时启用;MIP 机型不受影响,照常全量绘制。
|
||
|
||
实测(fenix847mm,取表盘内区):
|
||
|
||
| | 点亮像素数 | 平均亮度(功耗代理) |
|
||
|---|---|---|
|
||
| 普通模式 | 27.6% | 14.1% |
|
||
| 常亮模式 | **9.7%** | **2.5%** |
|
||
|
||
常亮下砍掉的东西:整条日期带底色、**整个进度环**、四个数据位与电池;保留的时间、冒号、日期压暗到 38%。
|
||
|
||
两个口径要分清:「点亮像素数」把每个抗锯齿边缘像素都算成亮的,对大号数字表盘天然不友好;AMOLED 的功耗更接近各子像素亮度之和,也就是表里的「平均亮度」。本实现两个口径都留了余量。
|
||
|
||
---
|
||
|
||
## 8.5 多语言
|
||
|
||
`manifest.xml` 声明了 `eng` 与 `chn`,两套文案分别在 `resources/strings/` 与 `resources-chn/strings/`,由 `monkey.jungle` 的 `base.resourcePath = resources;resources-chn` 同时纳入编译(目录名的 `-chn` 后缀就是语言标记)。手表会按系统语言自动选择。
|
||
|
||
**表盘上的文字也走资源**,不是硬编码:
|
||
|
||
| 资源 | 英文 | 中文 |
|
||
|------|------|------|
|
||
| `DowNames` | `SUN,MON,…,SAT` | `周日,周一,…,周六` |
|
||
| `MonNames` | `JAN,FEB,…,DEC` | `1月,2月,…,12月` |
|
||
| `DateFormat` | `$1$ $2$` → `SEP 9` | `$1$$2$日` → `9月9日` |
|
||
| `Meridiem` | `AM,PM` | `上午,下午` |
|
||
| `FieldLabels` | `TEMP,KCAL,STEP,DST,…` | `气温,卡路里,步数,距离,…` |
|
||
|
||
逗号分隔的列表在 `loadStrings()` 里一次性读入并拆分(`loadResource` 每次调用开销不低,不宜放进 `onUpdate`)。
|
||
|
||
> 中文字形依赖手表固件里的 APAC 字体包(`fontSet: apac_twcn` / `apac_chn`)。国行机型自带;国际版若未安装中文字体包,会退回英文或显示方块。模拟器可用 `Settings → Language → Chinese (Simplified)` 验证。
|
||
|
||
---
|
||
|
||
## 9. 构建
|
||
|
||
需安装 Connect IQ SDK 9.1.0,`monkeyc` 在 PATH 中,并备好 `developer_key.der`(**不在仓库内**,需自行生成或提供)。
|
||
|
||
```bash
|
||
monkeyc -o bin/Fenix8V3.prg -f monkey.jungle -y developer_key.der -d fenix847mm
|
||
```
|
||
|
||
全部 17 款设备批量编译(已验证通过):
|
||
|
||
```bash
|
||
for d in enduro3 fenix843mm fenix847mm fenix8pro47mm fenix8solar47mm fenix8solar51mm \
|
||
epix2 epix2pro47mm epix2pro51mm fenix7 fenix7x fenix7s fr965 fr265 fr255 \
|
||
venu3 vivoactive5; do
|
||
monkeyc -o "bin/Fenix8V3_${d}.prg" -f monkey.jungle -y developer_key.der -d "$d" \
|
||
&& echo "[$d] OK" || echo "[$d] FAIL"
|
||
done
|
||
```
|
||
|
||
屏幕分辨率跨度较大(FR255S 218×218 至 454×454 共 8 档),全部按 `s = width/500` 缩放。
|
||
|
||
机型列表和 `monkey.jungle` 由 `tools/gen_devices.py` 扫描本机已安装的 SDK 设备生成:筛选条件是「支持 watchFace + 圆屏 + CIQ ≥ 4.2」(Complications 需要 4.2)。**注意结果取决于本机装了哪些设备**,跑之前先确认 SDK 设备装全了,脚本会打印找到的数量。
|
||
|
||
---
|
||
|
||
## 10. 模拟器与侧载
|
||
|
||
```bash
|
||
connectiq & # 启动模拟器
|
||
monkeydo bin/Fenix8V3_fenix847mm.prg fenix847mm # 载入表盘
|
||
```
|
||
|
||
非 release 编译会同时产出 `bin/*-settings.json`,可在模拟器 `View → Settings` 载入以实时切换主题与指标。注意模拟器会**持久化**已保存的设置,改 `properties.xml` 的默认值对已运行过的实例不生效,需先重置应用数据。
|
||
|
||
真机侧载:用 Garmin Express 或 Connect 手机 App 导入对应机型的 `bin/Fenix8V3_<device>.prg`,或把 `.prg` 拷入手表 `/GARMIN/Apps/`。
|
||
|
||
---
|
||
|
||
## 11. 预览
|
||
|
||
```bash
|
||
python3 tools/gen_preview_svg.py
|
||
```
|
||
|
||
产出 `preview-themes.svg`(七套主题一览)与 `preview.html`(可切换主题的单页)。两者用网页字体渲染文字,**几何与配色可信,字形不可信** —— 真机字形以模拟器截图为准。
|
||
|
||
---
|
||
|
||
## 12. 与设计稿的差异
|
||
|
||
1. **字体**。设计稿用的是一款更粗、更方的第三方字体;Garmin 只提供内置 Roboto / Bionic 系列,且各机型不同。字号按 cap height 对齐,字形无法一致。
|
||
2. **图标**。设计稿里的图标是位图描出的密集矢量路径(数百个点),不适合逐帧绘制;代码用圆、多边形、线段按实测的子形状位置重建了电量、日出/日落、多云三个图标。其余数据项(卡路里 / 步数 / 距离 / 楼层)设计稿未提供图标,为自绘。
|
||
3. **底弧高段颜色**为外推值,见 §3。
|
||
4. **进度环是实时数据**,设计稿中的是固定演示状态(右上 21/21、左上 10/21、右下 9/21、左下 14/21)。
|
||
|
||
---
|
||
|
||
## 13. 设计稿版权
|
||
|
||
`design/` 内的 SVG、tokens 与 `rebuild.py` 来自 `watchface-kit`,版权归原作者所有。本仓库是按该设计稿实现的 Monkey C 表盘。
|