# Fenix 8 V3 — Garmin 数据表盘 按 `watchface-kit` 设计稿 1:1 还原的多指标数据表盘。设计画布 **500×500**,渲染时按屏宽等比缩放 `s = screenWidth / 500`,支持圆形 AMOLED(Fenix 8 / Epix 2 / Venu 3 等)与圆屏 MIP(Fenix 7 / Enduro 3 / FR255 等)共 17 款设备。 - **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_preview_svg.py # source/Themes.mc -> preview-themes.svg + preview.html ├── source/ │ ├── Fenix8V3App.mc # 应用入口 │ ├── Fenix8V3View.mc # 全部渲染逻辑 │ └── Themes.mc # ⚠️ 自动生成,勿手改 ├── resources/ # 英文文案 + 设置项定义 ├── resources-chn/ # 简体中文文案 └── bin/ # 编译产物(已 gitignore) ``` 三份实现(表盘 / SVG 预览 / HTML 预览)只有 **一份颜色数据**:`Themes.mc`。预览脚本直接解析它,不会与固件漂移。 --- ## 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. 可配置设置 共 13 项,通过 Garmin Connect 手机 App 的「表盘设置」修改(定义在 `resources/settings/settings.xml`,默认值在 `properties.xml`)。 | 设置 | 键 | 可选值 | 默认 | |------|----|--------|------| | 主题 | `Theme` | 1–7 | 1 | | 顶右 / 顶左 / 底右 / 底左弧指标 | `RingTR/TL/BR/BL` | 0 步数 / 1 卡路里 / 2 距离 / 3 楼层 / 4 动动条 | 0 / 1 / 4 / 3 | | 左上数据 | `LeftTop` | 见下 | 6(日出) | | 右上数据 | `RightTop` | 见下 | 8(最高/最低气温) | | 左下数据 | `BottomLeft` | 见下 | 3(距离) | | 右下数据 | `BottomRight` | 见下 | 2(步数) | | 温度单位 | `TempUnit` | 0 跟随手表 / 1 °C / 2 °F | 0 | | 显示顶行 / 日期带 / 底行 | `ShowTop` / `ShowBand` / `ShowBottom` | 布尔 | true | **数据项**(四个数据位共用同一张表):`0` 当前气温 · `1` 卡路里 · `2` 步数 · `3` 距离 · `4` 楼层 · `5` 电量 · `6` 日出 · `7` 日落 · `8` 最高/最低气温 · `9` 心率。 默认值即设计稿的配置:左上=日出时间、右上=`63°/52°`、左下=距离、右下=步数。 底部两格的小标签跟随所选数据自动变化(`距离` / `步数` / `心率` …),不需要单独配置。 --- ## 8. 数据来源 | 元素 | API | |------|-----| | 步数 / 卡路里 / 距离 / 楼层 / 动动条 | `ActivityMonitor.getInfo()` | | 日出 / 日落 | `Weather.getSunrise/getSunset(location, Time.now())`,位置取 `CurrentConditions.observationLocationPosition`,回退 `Activity.getActivityInfo().currentLocation` | | 最高 / 最低气温 | `Weather.getDailyForecast()[0].highTemperature / lowTemperature` | | 当前气温 | `Weather.getCurrentConditions().temperature` | | 电量 | `System.getSystemStats().battery`(0–100 的百分比) | | 时间 / 日期 | `Time.Gregorian`,`FORMAT_SHORT`(只有短格式的 `day_of_week` / `month` 是数字) | | 蓝牙 | `DeviceSettings.phoneConnected`,未连接时图标压暗 | 单位与制式跟随手表设置:`is24Hour`(24 小时制下不显示 AM/PM)、`distanceUnits`(km / mi)、`temperatureUnits`(°C / °F)。天气与日出需手表已配对并同步过数据,否则显示 `--`。 --- ## 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 ``` 屏幕分辨率跨度较大(fenix7s 240×240 至 fenix847mm / venu3 454×454),全部按 `s = width/500` 缩放。 --- ## 10. 模拟器与侧载 ```bash connectiq & # 启动模拟器 monkeydo bin/Fenix8V3_fenix847mm.prg fenix847mm # 载入表盘 ``` 非 release 编译会同时产出 `bin/*-settings.json`,可在模拟器 `View → Settings` 载入以实时切换主题与指标。注意模拟器会**持久化**已保存的设置,改 `properties.xml` 的默认值对已运行过的实例不生效,需先重置应用数据。 真机侧载:用 Garmin Express 或 Connect 手机 App 导入对应机型的 `bin/Fenix8V3_.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 表盘。