桌面动态天气小组件,原子卡片深色模式自动适配

📅2026/7/13 18:28:53 👁️次浏览
桌面动态天气小组件,原子卡片深色模式自动适配
天气卡片是桌面最常用的原子服务卡片之一不用打开应用一眼就能看到当前温度、天气状况和未来预报。很多新手做卡片时最头疼的就是深色模式适配——要么深浅色下文字看不清要么切换系统主题后卡片颜色不跟着变。其实鸿蒙原子卡片的深色模式适配有非常成熟的官方方案配置好资源文件后系统会自动切换无需手动判断。本文就带你从零实现一个高颜值天气服务卡片完整支持2×2 / 2×4双尺寸、深浅色模式自动适配、定时更新、点击跳转主应用全程基于鸿蒙 7API 26kit 标准规范代码可直接复制运行。一、核心设计深浅色适配的两种方案在正式写代码之前先搞清楚原子卡片深色模式适配的两种主流方案对应不同的业务场景。方案一资源文件自动适配推荐这是官方推荐的最佳实践也是最省心的方案在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.etsimport{FormExtensionAbility,formBindingData}fromkit.AbilityKit;import{hilog}fromkit.PerformanceAnalysisKit;constTAGWeatherFormAbility;exportdefaultclassWeatherFormAbilityextendsFormExtensionAbility{/** * 卡片首次添加到桌面时触发 */onCreate(formId:string):void{hilog.info(0x0000,TAG,天气卡片创建:${formId});constweatherDatathis.getMockWeatherData();formBindingData.createFormBindingData(weatherData);}/** * 定时更新 / 主动更新时触发 */onUpdate(formId:string):void{hilog.info(0x0000,TAG,天气卡片更新:${formId});constweatherDatathis.getMockWeatherData();formBindingData.updateForm(formId,weatherData);}/** * 处理卡片内点击事件 */onFormEvent(formId:string,message:string):void{hilog.info(0x0000,TAG,卡片事件:${message});if(messagerefresh){this.onUpdate(formId);}}/** * 模拟天气数据 * 实际项目中替换为真实天气接口请求 */privategetMockWeatherData():Recordstring,Object{// 模拟随机温度演示动态更新效果constcurrentTempMath.floor(Math.random()*15)20;consthighTempcurrentTemp5;constlowTempcurrentTemp-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{consthString(date.getHours()).padStart(2,0);constmString(date.getMinutes()).padStart(2,0);return${h}:${m}更新;}}四、第三步卡片 UI 实现 双尺寸自适应卡片 UI 是核心我们实现2×2 尺寸简洁展示城市、温度、天气状况2×4 尺寸额外增加未来三天预报、湿度风力等详细信息深浅色自动适配所有颜色引用资源系统自动切换新建entry/src/main/ets/widget/WeatherCard.etsComponentexportdefaultstruct WeatherCard{// 系统注入卡片尺寸用于多尺寸适配StateformWidth:number0;StateformHeight:number0;// 数据绑定字段与生命周期类中一一对应Propcity:string;Propcondition:string;PropweatherIcon:string;PropcurrentTemp:number0;ProphighTemp:number0;ProplowTemp:number0;Prophumidity:number0;Propwind:string;PropupdateTime:string;Propforecast:ArrayRecordstring,Object[];/** * 判断是否为 2×4 大号卡片 * 高度大于 400vp 判定为大号 */getisLargeSize():boolean{returnthis.formHeight400;}/** * 点击卡片整体跳转主应用 */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:Recordstring,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.jsonmodule.json5中colorMode必须设为auto颜色名称必须和 base 中完全一致大小写敏感3. 卡片显示加载失败占位图原因卡片 UI 代码语法错误或使用了卡片不支持的组件如 TextInput、Scroll 等解决卡片优先使用基础展示组件复杂交互建议跳转主应用实现查看 HiLog 报错定位具体问题4. 定时更新不生效原因updateDuration最小值为 30 分钟设置小于 30 的值不会生效解决确认配置 ≥ 30需要更高频更新使用主动刷新方式5. 多尺寸不生效原因supportDimensions格式写错星号写成乘号或中文符号解决尺寸格式必须为2*2英文星号高度阈值建议根据实际设备调整用区间判断更稳妥七、总结一个标准的天气原子卡片核心可以总结为三件事资源分层base dark 两套颜色资源实现深浅色自动适配尺寸自适应通过系统注入的宽高变量一套代码适配多种卡片尺寸数据驱动生命周期类负责数据供给UI 层纯渲染职责分离清晰深色模式适配看似细节却直接影响用户体验。用官方推荐的资源适配方案不仅代码简洁、性能优异还能保证和系统原生卡片体验一致是最稳妥的落地方式。