一、技术背景

1.1 项目概述

天气应用是移动端常见应用类型之一。本项目实现以下功能:

功能模块 技术要点
实时天气展示 状态绑定、数据驱动
动态主题切换 条件渐变背景
生活指数展示 @Builder组件复用
逐小时/7日预报 列表渲染、数据生成
城市切换 模态弹窗、状态联动

1.2 技术栈

  • 框架: ArkUI
  • 语言: ArkTS
  • 模型: Stage Model
  • 工具: DevEco Studio

二、数据模型设计

2.1 接口定义

// 天气主数据
interface WeatherInfo {
  city: string
  temp: number
  cond: string
  humid: number
  wind: string
  uv: string
  high: number
  low: number
  aqi: number
  aqiDesc: string
}

// 小时预报
interface HourlyItem {
  time: string
  temp: number
  icon: string
}

// 日预报
interface DailyItem {
  day: string
  date: string
  icon: string
  high: number
  low: number
  desc: string
}

2.2 数据源

项目采用本地模拟数据,支持8个城市:

private readonly CITIES: string[] = [
  '北京市', '上海市', '广州市', '深圳市',
  '杭州市', '成都市', '武汉市', '南京市'
]

private readonly WEATHER_DATA: WeatherInfo[] = [
  { city: '北京市', temp: 26, cond: '晴', humid: 45, wind: '3级', 
    uv: '中等', high: 30, low: 18, aqi: 72, aqiDesc: '良' },
  { city: '上海市', temp: 24, cond: '多云', humid: 62, wind: '4级', 
    uv: '中等', high: 27, low: 20, aqi: 55, aqiDesc: '良' },
  { city: '广州市', temp: 31, cond: '雷阵雨', humid: 78, wind: '3级', 
    uv: '强', high: 33, low: 25, aqi: 38, aqiDesc: '优' },
  // ... 其他城市数据
]

三、状态管理

3.1 状态变量定义

@Entry
@Component
struct Index {
  // 基础天气信息
  @State location: string = '北京市'
  @State currentTemp: number = 26
  @State currentCondition: string = '晴'
  
  // 生活指数
  @State currentHumidity: number = 45
  @State currentWind: string = '3级'
  @State currentUV: string = '中等'
  
  // 温度范围
  @State currentHigh: number = 30
  @State currentLow: number = 18
  
  // 空气质量
  @State currentAQI: number = 72
  @State currentAQIDesc: string = '良'
  
  // UI状态
  @State showCityPicker: boolean = false
  
  // 预报数据
  @State hourlyData: HourlyItem[]
  @State dailyData: DailyItem[]
}

3.2 状态联动机制

城市切换时,所有状态同步更新:

private switchCity(city: string): void {
  const data = this.getWeatherByCity(city)
  
  // 批量状态更新
  this.location = city
  this.currentTemp = data.temp
  this.currentCondition = data.cond
  this.currentHumidity = data.humid
  this.currentWind = data.wind
  this.currentUV = data.uv
  this.currentHigh = data.high
  this.currentLow = data.low
  this.currentAQI = data.aqi
  this.currentAQIDesc = data.aqiDesc
  
  // 重新生成预报数据
  this.hourlyData = this.generateHourlyData(data.cond, data.temp)
  this.dailyData = this.generateDailyData(data.cond, data.high, data.low)
  
  // 关闭弹窗
  this.showCityPicker = false
}

四、核心实现

4.1 动态渐变背景

根据天气状况动态计算渐变色:

private getBgGradient(cond: string): string {
  const gradientMap: Record<string, string> = {
    '晴': '#FF9F0A',
    '多云': '#8E8E93',
    '阴': '#8E8E93',
    '小雨': '#5AC8FA',
    '阵雨': '#5AC8FA',
    '雷阵雨': '#5AC8FA'
  }
  return gradientMap[cond] || '#4A90D9'
}

private getBgEnd(cond: string): string {
  const endMap: Record<string, string> = {
    '晴': '#FFD60A',
    '多云': '#636366',
    '阴': '#636366',
    '小雨': '#007AFF',
    '阵雨': '#007AFF',
    '雷阵雨': '#007AFF'
  }
  return endMap[cond] || '#87CEEB'
}

应用渐变:

Column() {
  // ...
}
.linearGradient({
  direction: GradientDirection.Bottom,
  colors: [
    [this.getBgGradient(this.currentCondition), 0],
    [this.getBgEnd(this.currentCondition), 1]
  ]
})

主题效果:

天气 起始色 结束色 视觉效果
#FF9F0A #FFD60A 温暖明亮
多云/阴 #8E8E93 #636366 沉稳内敛
#5AC8FA #007AFF 清新凉爽

4.2 温度条可视化

温度条宽度与颜色根据温度动态计算:

// 宽度计算:基于温差
private tempBarWidth(low: number, high: number): string {
  const diff = high - low
  const width = Math.floor((diff / 20) * 100 + 20)
  return width + '%'
}

// 颜色计算:基于平均温度
private tempBarColor(low: number, high: number): string {
  const avg = (low + high) / 2
  if (avg >= 28) return '#FF3B30'   // 红色-热
  if (avg >= 20) return '#FF9F0A'   // 橙色-温
  return '#34C759'                   // 绿色-凉
}

4.3 数据生成算法

逐小时预报:

private generateHourlyData(cond: string, baseTemp: number): HourlyItem[] {
  const times = ['现在', '15:00', '18:00', '21:00', '00:00', '03:00', '06:00', '09:00']
  const icons = ['🌙', '🌙', '🌤️', '☀️', '☀️', '☀️', '🌤️', '🌙']
  
  return times.map((time, i) => ({
    time,
    temp: i === 0 ? baseTemp : baseTemp + Math.floor(Math.random() * 6) - 3,
    icon: icons[i]
  }))
}

7日预报:

private generateDailyData(cond: string, high: number, low: number): DailyItem[] {
  const days = ['今天', '明天', '周三', '周四', '周五', '周六', '周日']
  const icons = ['☀️', '⛅', '🌧️', '☁️', '☀️', '☀️', '⛅']
  const descs = ['晴', '多云', '小雨', '阴', '晴', '晴', '多云']
  
  return days.map((day, i) => ({
    day,
    date: '6月' + (i + 1) + '日',
    icon: icons[i],
    high: high + Math.floor(Math.random() * 6) - 3,
    low: low + Math.floor(Math.random() * 4) - 2,
    desc: descs[i]
  }))
}

五、组件封装

5.1 @Builder装饰器

@Builder 用于封装可复用的UI描述,支持参数传递。

5.2 信息卡片组件

@Builder compactCard(icon: string, value: string, desc: string, color: string) {
  Column() {
    Text(icon).fontSize(20)
    Text(value)
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
      .margin({ top: 6 })
    if (desc.length > 0) {
      Text(desc)
        .fontSize(12)
        .fontColor(color)
        .margin({ top: 2 })
    }
  }
  .layoutWeight(1)
  .padding({ top: 10, bottom: 10 })
  .alignItems(HorizontalAlign.Center)
}

5.3 小时预报项组件

@Builder hourlyItem(item: HourlyItem) {
  Column() {
    Text(item.time)
      .fontSize(12)
      .fontColor('#8E8E93')
    Text(item.icon)
      .fontSize(22)
      .margin({ top: 6 })
    Text(String(item.temp) + '°')
      .fontSize(15)
      .fontWeight(FontWeight.Medium)
      .fontColor(Color.White)
      .margin({ top: 6 })
  }
  .padding({ left: 10, right: 10 })
  .alignItems(HorizontalAlign.Center)
}

5.4 日预报行组件

@Builder dailyRow(item: DailyItem) {
  Row() {
    Text(item.day)
      .fontSize(15)
      .fontColor(Color.White)
      .width(48)
    Text(item.icon)
      .fontSize(18)
      .width(30)
    Text(item.desc)
      .fontSize(14)
      .fontColor('#8E8E93')
      .width(36)
    Blank()
    Text(String(item.low) + '°')
      .fontSize(14)
      .fontColor('#8E8E93')
      .width(32)
      .textAlign(TextAlign.End)
    
    // 温度条容器
    Column() {
      Column()
        .width(this.tempBarWidth(item.low, item.high))
        .height(6)
        .borderRadius(3)
        .backgroundColor(this.tempBarColor(item.low, item.high))
    }
    .width(60)
    .height(6)
    .backgroundColor('#333333')
    .borderRadius(3)
    .margin({ left: 4, right: 4 })
    
    Text(String(item.high) + '°')
      .fontSize(14)
      .fontColor(Color.White)
      .width(32)
      .textAlign(TextAlign.End)
  }
  .width('100%')
  .padding({ left: 16, right: 16, top: 6, bottom: 6 })
}

5.5 城市按钮组件

@Builder cityButton(city: string) {
  Button(city)
    .fontSize(14)
    .fontWeight(FontWeight.Medium)
    .height(44)
    .borderRadius(22)
    .backgroundColor(this.location === city ? '#FF9F0A' : '#2C2C2E')
    .fontColor(this.location === city ? Color.White : '#8E8E93')
    .layoutWeight(1)
    .margin({ left: 4, right: 4 })
    .onClick(() => this.switchCity(city))
}

六、页面布局

6.1 布局结构

Stack
├── Scroll (主内容)
│   └── Column
│       ├── Column (天气展示区 - 渐变背景)
│       ├── Row (信息卡片)
│       ├── Column (逐小时预报)
│       ├── Column (7天预报)
│       └── Text (更新时间)
└── Column (城市选择弹窗 - 条件渲染)

6.2 模态弹窗实现

if (this.showCityPicker) {
  Column() {
    Column() {
      Text('选择城市')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .margin({ top: 20, bottom: 16 })
      
      Row() {
        this.cityButton(this.CITIES[0])
        this.cityButton(this.CITIES[1])
        this.cityButton(this.CITIES[2])
        this.cityButton(this.CITIES[3])
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      
      Row() {
        this.cityButton(this.CITIES[4])
        this.cityButton(this.CITIES[5])
        this.cityButton(this.CITIES[6])
        this.cityButton(this.CITIES[7])
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .margin({ top: 8 })
      
      Button('取消')
        .fontSize(16)
        .fontColor('#FF9F0A')
        .backgroundColor('#2C2C2E')
        .width('90%')
        .height(44)
        .borderRadius(22)
        .margin({ top: 16, bottom: 20 })
        .onClick(() => this.showCityPicker = false)
    }
    .width('85%')
    .backgroundColor('#1C1C1E')
    .borderRadius(20)
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#80000000')
  .justifyContent(FlexAlign.Center)
  .onClick(() => this.showCityPicker = false)
}

七、运行验证

7.1 构建命令

# DevEco Studio菜单
Build > Build Hap(s)/APP(s) > Build Hap(s)

7.2 运行效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


八、技术要点总结

技术点 实现方式
状态驱动 @State装饰器
组件复用 @Builder装饰器
动态主题 条件渐变背景
温度可视化 动态宽度+颜色计算
模态弹窗 Stack叠加+条件渲染
数据生成 随机算法+基准值偏移

九、扩展方向

扩展项 技术方案
真实API HTTP请求 + JSON解析
地理定位 @ohos.geoLocationManager
数据缓存 @ohos.data.preferences
天气动画 Canvas / Lottie
下拉刷新 Refresh组件

十、结语

本文详细讲解了鸿蒙天气App的实现过程,涵盖动态主题、数据可视化、组件封装等核心内容。通过本项目,可快速掌握ArkUI声明式开发的核心范式。


参考资料:

  1. ArkUI开发指南
  2. HarmonyOS应用开发文档
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐