桌面动态天气小组件,原子卡片深色模式自动适配
天气卡片是桌面最常用的原子服务卡片之一:不用打开应用,一眼就能看到当前温度、天气状况和未来预报。很多新手做卡片时最头疼的就是深色模式适配——要么深浅色下文字看不清,要么切换系统主题后卡片颜色不跟着变。
其实鸿蒙原子卡片的深色模式适配有非常成熟的官方方案,配置好资源文件后系统会自动切换,无需手动判断。本文就带你从零实现一个高颜值天气服务卡片,完整支持2×2 / 2×4双尺寸、深浅色模式自动适配、定时更新、点击跳转主应用,全程基于鸿蒙 7(API 26)@kit 标准规范,代码可直接复制运行。
一、核心设计:深浅色适配的两种方案
在正式写代码之前,先搞清楚原子卡片深色模式适配的两种主流方案,对应不同的业务场景。
方案一:资源文件自动适配(推荐)
这是官方推荐的最佳实践,也是最省心的方案:
- 在
resources/base下放浅色模式颜色资源 - 在
resources/dark下放深色模式颜色资源 - 代码中通过
$r('app.color.xxx')引用 - 系统切换深浅色时,自动加载对应目录下的资源,无需写任何判断逻辑
✅优势:零代码判断、系统级调度、性能最优、适配最稳定
❌不足:适合纯色、图片资源适配,复杂渐变需要额外处理
方案二:代码动态判断
通过@Environment('colorMode')环境变量获取当前主题,代码里手动切换颜色。
- 适合需要根据主题做复杂逻辑判断、动态渐变的场景
- 写法相对繁琐,状态维护成本更高
本文实战采用方案一,符合官方规范,新手也能一次写对。
二、第一步:工程基础配置
2.1 颜色资源配置
这是深色模式自动适配的核心。我们先定义两套颜色资源,系统会根据当前主题自动加载。
浅色模式颜色(默认)
打开entry/src/main/resources/base/element/color.json,添加:
{"color":[{"name":"weather_card_bg","value":"#FFFFFF"},{"name":"weather_card_text_primary","value":"#1C1C1E"},{"name":"weather_card_text_secondary","value":"#8E8E93"},{"name":"weather_card_divider","value":"#E5E5EA"},{"name":"weather_card_accent","value":"#0A59F7"},{"name":"weather_card_temp_high","value":"#FF6B35"},{"name":"weather_card_temp_low","value":"#4DABF7"}]}深色模式颜色
手动创建目录entry/src/main/resources/dark/element/,新建color.json:
{"color":[{"name":"weather_card_bg","value":"#2C2C2E"},{"name":"weather_card_text_primary","value":"#F2F2F7"},{"name":"weather_card_text_secondary","value":"#8E8E93"},{"name":"weather_card_divider","value":"#3A3A3C"},{"name":"weather_card_accent","value":"#4DABF7"},{"name":"weather_card_temp_high","value":"#FF8A5B"},{"name":"weather_card_temp_low","value":"#74C0FC"}]}关键规则:两个文件中的颜色名称必须完全一致,值不同即可。系统切换主题时,会自动根据名称匹配对应目录下的颜色值。
2.2 卡片清单配置
打开entry/src/main/module.json5,在extensionAbilities中添加天气卡片的声明。
核心配置:colorMode: "auto"开启自动深浅色适配,updateDuration设置定时更新周期。
"extensionAbilities": [ { "name": "WeatherFormAbility", "srcEntry": "./ets/form/WeatherFormAbility.ets", "description": "$string:weather_form_desc", "type": "form", "icon": "$media:icon", "label": "$string:weather_form_label", "formsEnabled": true, "forms": [ { "name": "WeatherCard", "displayName": "$string:weather_card_name", "description": "$string:weather_form_desc", "src": "./ets/widget/WeatherCard.ets", "uiSyntax": "arkts", "window": { "designWidth": 720, "autoDesignWidth": false }, "colorMode": "auto", "formVisibleNotify": true, "isDefault": true, "updateEnabled": true, "updateDuration": 30, "defaultDimension": "2*2", "supportDimensions": ["2*2", "2*4"] } ] } ]关键配置说明:
colorMode: "auto":开启自动深浅色适配,配合资源文件实现无缝切换updateDuration: 30:每 30 分钟自动更新一次天气数据(最小值 30 分钟)supportDimensions:声明支持 2×2 中号和 2×4 大号两种尺寸isDefault: true:设为默认卡片
三、第二步:卡片生命周期实现
卡片有独立的生命周期,负责数据提供、定时更新、事件响应。
新建entry/src/main/ets/form/WeatherFormAbility.ets:
import{FormExtensionAbility,formBindingData}from'@kit.AbilityKit';import{hilog}from'@kit.PerformanceAnalysisKit';constTAG='WeatherFormAbility';exportdefaultclassWeatherFormAbilityextendsFormExtensionAbility{/** * 卡片首次添加到桌面时触发 */onCreate(formId:string):void{hilog.info(0x0000,TAG,`天气卡片创建:${formId}`);constweatherData=this.getMockWeatherData();formBindingData.createFormBindingData(weatherData);}/** * 定时更新 / 主动更新时触发 */onUpdate(formId:string):void{hilog.info(0x0000,TAG,`天气卡片更新:${formId}`);constweatherData=this.getMockWeatherData();formBindingData.updateForm(formId,weatherData);}/** * 处理卡片内点击事件 */onFormEvent(formId:string,message:string):void{hilog.info(0x0000,TAG,`卡片事件:${message}`);if(message==='refresh'){this.onUpdate(formId);}}/** * 模拟天气数据 * 实际项目中替换为真实天气接口请求 */privategetMockWeatherData():Record<string,Object>{// 模拟随机温度,演示动态更新效果constcurrentTemp=Math.floor(Math.random()*15)+20;consthighTemp=currentTemp+5;constlowTemp=currentTemp-8;return{city:'郑州市',condition:'晴',weatherIcon:'☀️',currentTemp:currentTemp,highTemp:highTemp,lowTemp:lowTemp,humidity:45,wind:'东北风 3级',updateTime:this.formatTime(newDate()),// 未来3天预报(仅2×4尺寸显示)forecast:[{day:'今天',icon:'☀️',high:highTemp,low:lowTemp},{day:'明天',icon:'⛅',high:highTemp-1,low:lowTemp-2},{day:'后天',icon:'🌧️',high:highTemp-4,low:lowTemp-3}]};}privateformatTime(date:Date):string{consth=String(date.getHours()).padStart(2,'0');constm=String(date.getMinutes()).padStart(2,'0');return`${h}:${m}更新`;}}四、第三步:卡片 UI 实现 + 双尺寸自适应
卡片 UI 是核心,我们实现:
- 2×2 尺寸:简洁展示城市、温度、天气状况
- 2×4 尺寸:额外增加未来三天预报、湿度风力等详细信息
- 深浅色自动适配:所有颜色引用资源,系统自动切换
新建entry/src/main/ets/widget/WeatherCard.ets:
@Componentexportdefaultstruct WeatherCard{// 系统注入:卡片尺寸,用于多尺寸适配@StateformWidth:number=0;@StateformHeight:number=0;// 数据绑定字段,与生命周期类中一一对应@Propcity:string='';@Propcondition:string='';@PropweatherIcon:string='';@PropcurrentTemp:number=0;@ProphighTemp:number=0;@ProplowTemp:number=0;@Prophumidity:number=0;@Propwind:string='';@PropupdateTime:string='';@Propforecast:Array<Record<string,Object>>=[];/** * 判断是否为 2×4 大号卡片 * 高度大于 400vp 判定为大号 */getisLargeSize():boolean{returnthis.formHeight>400;}/** * 点击卡片整体跳转主应用 */handleJumpApp(){postCardAction(this,{action:'router',params:{uri:'pages/Index'}});}/** * 点击刷新按钮,手动触发更新 */handleRefresh(){postCardAction(this,{action:'event',params:{message:'refresh'}});}build(){Column({space:this.isLargeSize?12:8}){// ========== 顶部:城市 + 刷新 ==========Row(){Text(this.city).fontSize(14).fontColor($r('app.color.weather_card_text_secondary')).layoutWeight(1);Text('刷新').fontSize(12).fontColor($r('app.color.weather_card_accent')).onClick(()=>this.handleRefresh());}.width('100%');// ========== 中部:天气图标 + 温度 ==========Row({space:8}){Text(this.weatherIcon).fontSize(this.isLargeSize?48:36);Column({space:2}){Text(`${this.currentTemp}°`).fontSize(this.isLargeSize?42:32).fontWeight(FontWeight.Bold).fontColor($r('app.color.weather_card_text_primary'));Text(this.condition).fontSize(13).fontColor($r('app.color.weather_card_text_secondary'));}.alignItems(HorizontalAlign.Start);Blank();// 仅大号显示高低温if(this.isLargeSize){Column({space:4}){Text(`↑${this.highTemp}°`).fontSize(14).fontColor($r('app.color.weather_card_temp_high'));Text(`↓${this.lowTemp}°`).fontSize(14).fontColor($r('app.color.weather_card_temp_low'));}.alignItems(HorizontalAlign.End);}}.width('100%');// ========== 仅大号:详细信息 + 预报 ==========if(this.isLargeSize){// 湿度风力Row(){Text(`💧${this.humidity}%`).fontSize(12).fontColor($r('app.color.weather_card_text_secondary'));Blank();Text(this.wind).fontSize(12).fontColor($r('app.color.weather_card_text_secondary'));}.width('100%');Divider().color($r('app.color.weather_card_divider'));// 三天预报Row(){ForEach(this.forecast,(day:Record<string,Object>)=>{Column({space:6}){Text(day['day']asstring).fontSize(12).fontColor($r('app.color.weather_card_text_secondary'));Text(day['icon']asstring).fontSize(20);Text(`${day['high']}° /${day['low']}°`).fontSize(11).fontColor($r('app.color.weather_card_text_primary'));}.layoutWeight(1);})}.width('100%');}Blank();// ========== 底部:更新时间 ==========Text(this.updateTime).fontSize(11).fontColor($r('app.color.weather_card_text_secondary')).width('100%').textAlign(this.isLargeSize?TextAlign.End:TextAlign.Start);}.width('100%').height('100%').padding(16).backgroundColor($r('app.color.weather_card_bg')).borderRadius(16).onClick(()=>this.handleJumpApp());}}代码核心亮点
- 深浅色零代码适配:所有颜色统一使用
$r('app.color.xxx')引用资源,系统自动切换对应主题的色值 - 双尺寸自适应:通过系统注入的
formHeight判断卡片尺寸,条件渲染不同粒度的信息 - 双交互入口:整体点击跳转应用,刷新按钮点击触发实时更新
- 信息分层:小尺寸突出核心温度,大尺寸补充预报与详情,符合「按需展示」的卡片设计原则
五、运行验证步骤
- 编译运行:将应用安装到鸿蒙设备上
- 添加卡片:回到桌面,双指捏合进入编辑模式 → 服务卡片 → 找到你的应用
- 验证尺寸:分别添加 2×2 和 2×4 两种尺寸到桌面,对比布局差异
- 验证更新:点击卡片上的「刷新」按钮,温度和时间会实时变化
- 验证深色模式:
- 打开系统设置 → 显示与亮度 → 切换深色模式
- 回到桌面,卡片会自动切换为深色配色,文字、背景、分割线全部同步变化
- 切换回浅色模式,卡片自动恢复浅色样式
六、新手高频踩坑避坑指南
1. 切换深色模式卡片颜色不变
- 最常见原因:颜色硬编码写死了
#FFFFFF,没有使用资源引用 - 解决:所有颜色统一通过
$r('app.color.xxx')引用,确保 base 和 dark 目录下都有对应名称的颜色
2. dark 目录创建了还是不生效
- 排查点:
- 目录名称必须是
dark,全小写,不能写错 - 目录层级必须是
resources/dark/element/color.json module.json5中colorMode必须设为"auto"- 颜色名称必须和 base 中完全一致,大小写敏感
- 目录名称必须是
3. 卡片显示加载失败占位图
- 原因:卡片 UI 代码语法错误,或使用了卡片不支持的组件(如 TextInput、Scroll 等)
- 解决:卡片优先使用基础展示组件,复杂交互建议跳转主应用实现;查看 HiLog 报错定位具体问题
4. 定时更新不生效
- 原因:
updateDuration最小值为 30 分钟,设置小于 30 的值不会生效 - 解决:确认配置 ≥ 30;需要更高频更新使用主动刷新方式
5. 多尺寸不生效
- 原因:
supportDimensions格式写错,星号写成乘号或中文符号 - 解决:尺寸格式必须为
"2*2"(英文星号);高度阈值建议根据实际设备调整,用区间判断更稳妥
七、总结
一个标准的天气原子卡片,核心可以总结为三件事:
- 资源分层:base + dark 两套颜色资源,实现深浅色自动适配
- 尺寸自适应:通过系统注入的宽高变量,一套代码适配多种卡片尺寸
- 数据驱动:生命周期类负责数据供给,UI 层纯渲染,职责分离清晰
深色模式适配看似细节,却直接影响用户体验。用官方推荐的资源适配方案,不仅代码简洁、性能优异,还能保证和系统原生卡片体验一致,是最稳妥的落地方式。
