
文章目录前言城市天气数据是一张主卡片的来源当前城市用 selectedCity 索引表示背景色不是固定装饰而是天气状态的反馈主卡片的三个指标都是 current 派生出来的预报区域现在是静态演示数据这个页面适合继续补真实数据层完整代码最后总结前言天气页最考验的是“切换城市以后哪些东西应该跟着变”。温度、天气状态、背景色、湿度、风力当然要变小时预报和未来 7 天预报要不要变就取决于数据结构有没有按城市拆开。这个示例做的是一个轻量版多城市天气卡片城市列表和当前城市数据是联动的逐小时预报和 7 天预报则是固定展示数据。它不是完整天气应用但很适合练 ArkUI 里的横向城市选择、主卡片渐变背景和列表型预报信息。城市天气数据是一张主卡片的来源CityWeather_kxri把主天气卡片需要的信息都放在一起城市名、当前温度、天气描述、图标、最高最低温、湿度和风向风速。interfaceCityWeather_kxri{city:stringtemp:numbercondition:stringicon:stringhigh:numberlow:numberhumidity:numberwind:string}这组字段刚好对应主卡片里的几块区域顶部城市选择、中央大号温度、天气描述、最高最低温以及下面的湿度、风力、温差三列指标。读这类页面时我一般先把数据字段和 UI 区块对上这样不会被一堆Column、Row绕晕。hourly单独用HourlyForecast[]保存因为逐小时预报只需要时间、温度和图标。interfaceHourlyForecast{time:stringtemp:numbericon:string}但要注意当前hourly不是按城市维护的。切换北京、上海、广州、成都时主卡片会变逐小时预报不会变。这不是讲解时要掩盖的问题反而是理解源码边界的关键。当前城市用 selectedCity 索引表示页面没有把当前城市对象复制一份到状态里而是保存selectedCity这个索引再通过 getter 取出当前城市。StateselectedCity:number0getcurrent():CityWeather_kxri{returnthis.cities[this.selectedCity]}这个设计比较适合城市列表这种固定顺序数据。城市 tab 点击时只需要更新索引所有读取this.current的地方都会跟着刷新。ForEach(this.cities,(c:CityWeather_kxri,i:number){Text(c.city).fontColor(this.selectedCityi?#FFF:#FFFFFF99).fontWeight(this.selectedCityi?FontWeight.Bold:FontWeight.Normal).onClick(()this.selectedCityi)})这里还顺便用selectedCity i做了选中态样式。也就是说一个状态同时驱动两件事当前展示哪座城市以及顶部城市名哪个高亮。背景色不是固定装饰而是天气状态的反馈主卡片的渐变背景来自bgColor()。它读取当前城市的condition再返回两段颜色。bgColor():string[]{constcthis.current.conditionif(c晴天)return[#FFB347,#FF6B35]if(c多云)return[#B0BEC5,#78909C]if(c.includes(雨))return[#546E7A,#263238]return[#90A4AE,#607D8B]}这段逻辑让天气状态不仅体现在文字和图标上也体现在页面气氛上。晴天偏暖色雨天偏深色多云偏灰蓝用户切城市时会明显感觉到状态变化。不过这里也有一个维护点bgColor()每次返回数组UI 里又通过this.bgColor()[0]、this.bgColor()[1]取两次。.linearGradient({colors:[[this.bgColor()[0],0.0],[this.bgColor()[1],1.0]],angle:160})示例里没问题但如果这个函数以后变复杂可以先取成局部配置或者改成返回对象避免重复调用和魔法下标。主卡片的三个指标都是 current 派生出来的湿度和风向风速直接来自current温差则是high - low算出来的。Text(${this.current.humidity}%)Text(this.current.wind)Text(${this.current.high-this.current.low}°)温差没有单独存在数据里这个选择是对的。只要最高温和最低温存在温差就是派生信息单独保存只会增加同步风险。如果后续接真实天气接口建议保持这个思路接口返回原始天气字段页面里只保存必要状态能由当前天气算出来的就现场计算。预报区域现在是静态演示数据逐小时预报来自hourly用横向Scroll Row ForEach展示。这个结构适合移动端因为小时数据通常比屏幕宽横向扫起来很自然。Scroll(){Row(){ForEach(this.hourly,(h:HourlyForecast){Column(){Text(h.time)Text(h.icon)Text(${h.temp}°)}})}}.scrollable(ScrollDirection.Horizontal)未来 7 天预报则更简单直接在ForEach里遍历星期数组再通过同下标取图标和温度数组。ForEach([周二,周三,周四,周五,周六,周日,周一],(day:string,i:number){Text([☀️,⛅,️,☀️,⛅,☁️,☀️][i])Text(${[30,27,24,31,28,26,32][i]}° /${[20,18,17,21,19,17,22][i]}°)})这种写法适合 demo不适合长期维护。正式项目里我会把 7 天预报也建成对象数组例如{ day, icon, high, low }这样字段关系更清楚也不怕数组长度对不上。这个页面适合继续补真实数据层如果继续往真实天气应用靠第一步不是加动画而是把hourly和 7 天预报都挂到城市维度上。切换城市时主卡片、小时预报和 7 天预报都应该使用同一份城市天气数据。第二步才是接接口、做定位、处理加载态和错误态。当前示例已经把展示层骨架搭好了剩下的问题主要是数据结构要从“演示静态数据”升级成“按城市组织的天气详情”。完整代码下面保留案例完整代码方便你直接对照学习、复制和重构。import{router}fromkit.ArkUIinterfaceCityWeather_kxri{city:stringtemp:numbercondition:stringicon:stringhigh:numberlow:numberhumidity:numberwind:string}interfaceHourlyForecast{time:stringtemp:numbericon:string}EntryComponentstruct MultiCityWeatherCards{Statecities:CityWeather_kxri[][{city:北京,temp:28,condition:晴天,icon:☀️,high:32,low:20,humidity:45,wind:东南 3级},{city:上海,temp:24,condition:多云,icon:⛅,high:27,low:19,humidity:72,wind:东 2级},{city:广州,temp:33,condition:小雨,icon:️,high:35,low:27,humidity:88,wind:南 2级},{city:成都,temp:22,condition:阴天,icon:☁️,high:25,low:18,humidity:80,wind:西 1级},]StateselectedCity:number0Statehourly:HourlyForecast[][{time:现在,temp:28,icon:☀️},{time:14:00,temp:30,icon:☀️},{time:16:00,temp:29,icon:⛅},{time:18:00,temp:27,icon:⛅},{time:20:00,temp:24,icon:},{time:22:00,temp:22,icon:},]getcurrent():CityWeather_kxri{returnthis.cities[this.selectedCity]}bgColor():string[]{constcthis.current.conditionif(c晴天)return[#FFB347,#FF6B35]if(c多云)return[#B0BEC5,#78909C]if(c.includes(雨))return[#546E7A,#263238]return[#90A4AE,#607D8B]}build(){Column(){// Main weather cardColumn(){// City selectorRow(){Image($r(app.media.ic_back)).width(24).height(24).onClick(()router.back())Scroll(){Row(){ForEach(this.cities,(c:CityWeather_kxri,i:number){Text(c.city).fontSize(14).fontColor(this.selectedCityi?#FFF:#FFFFFF99).fontWeight(this.selectedCityi?FontWeight.Bold:FontWeight.Normal).padding({left:12,right:12}).onClick(()this.selectedCityi)})}}.scrollable(ScrollDirection.Horizontal).layoutWeight(1)}.width(100%).padding({left:16,right:16,top:16})// TemperatureColumn(){Text(this.current.icon).fontSize(80).margin({top:20,bottom:8})Text(\\${this.current.temp}°\).fontSize(72).fontWeight(FontWeight.Lighter).fontColor(#FFF)Text(this.current.condition).fontSize(18).fontColor(#FFFFFFCC).margin({bottom:8})Text(\最高 \${this.current.high}°/最低 \${this.current.low}°\).fontSize(14).fontColor(#FFFFFFAA)}.alignItems(HorizontalAlign.Center).margin({bottom:20})// StatsRow(){Column(){Text().fontSize(20)Text(\\${this.current.humidity}%\).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#FFF)Text(湿度).fontSize(12).fontColor(#FFFFFFAA)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text().fontSize(20)Text(this.current.wind).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#FFF)Text(风向风速).fontSize(12).fontColor(#FFFFFFAA)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text(️).fontSize(20)Text(\\${this.current.high-this.current.low}°\).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#FFF)Text(温差).fontSize(12).fontColor(#FFFFFFAA)}.layoutWeight(1).alignItems(HorizontalAlign.Center)}.width(100%).padding({left:16,right:16,bottom:20})}.width(100%).linearGradient({colors:[[this.bgColor()[0],0.0],[this.bgColor()[1],1.0]],angle:160})// Hourly forecastColumn(){Text(今日逐小时预报).fontSize(14).fontColor(#333).fontWeight(FontWeight.Bold).width(100%).padding({left:16,top:16,bottom:12})Scroll(){Row(){ForEach(this.hourly,(h:HourlyForecast){Column(){Text(h.time).fontSize(12).fontColor(#888)Text(h.icon).fontSize(24).margin({top:8,bottom:8})Text(\\${h.temp}°\).fontSize(15).fontWeight(FontWeight.Bold).fontColor(#333)}.alignItems(HorizontalAlign.Center).padding({left:16,right:16})})}}.scrollable(ScrollDirection.Horizontal).width(100%).padding({bottom:16})}.backgroundColor(#FFF).margin({top:1})// 7-day forecastText(未来7天).fontSize(14).fontColor(#333).fontWeight(FontWeight.Bold).width(100%).padding({left:16,top:16,bottom:8}).backgroundColor(#FFF)List(){ForEach([周二,周三,周四,周五,周六,周日,周一],(day:string,i:number){ListItem(){Row(){Text(day).fontSize(14).fontColor(#555).width(48)Text([☀️,⛅,️,☀️,⛅,☁️,☀️][i]).fontSize(20).layoutWeight(1).textAlign(TextAlign.Center)Text(\\${[30,27,24,31,28,26,32][i]}°/\${[20,18,17,21,19,17,22][i]}°\).fontSize(13).fontColor(#666).width(90).textAlign(TextAlign.End)}.width(100%).padding({left:16,right:16,top:10,bottom:10})}.border({width:{bottom:1},color:#F0F0F0})})}.backgroundColor(#FFF).layoutWeight(1)}.width(100%).height(100%).backgroundColor(#F0F4F8)}}最后总结多城市天气卡片 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。