鸿蒙开发入门:手把手教你做一个精美天气App
前言
最近在学鸿蒙,做了个小天气App练手。说实话,一开始觉得天气App不就展示个温度嘛,能有多难?结果做下来发现,要把UI做得好看、交互做得顺滑,还是有不少细节要处理的。
这篇文章把整个过程记录下来,希望能帮到同样在学习鸿蒙的小伙伴。
一、这是个啥项目?
就是一个天气应用,主要功能有:
- 显示当前城市的天气(温度、天气状况)
- 空气质量、紫外线、湿度、风速这些生活指数
- 逐小时预报(8小时)
- 7天天气预报
- 支持切换城市(8个城市)
- 根据天气自动换背景颜色(晴天橙色、雨天蓝色、阴天灰色)
二、创建项目
打开DevEco Studio,选 Empty Ability 模板。
填写信息:
- 项目名:WeatherApp
- 包名:com.example.weatherapp
- 保存位置:你自己选
一路Next,项目就建好了。
项目目录不用太在意,主要代码都在 entry/src/main/ets/pages/Index.ets 这个文件里。
三、数据怎么设计?
3.1 天气信息
我用一个接口定义天气数据:
interface WeatherInfo {
city: string // 城市名
temp: number // 当前温度
cond: string // 天气状况
humid: number // 湿度
wind: string // 风力
uv: string // 紫外线
high: number // 最高温
low: number // 最低温
aqi: number // 空气质量指数
aqiDesc: string // 空气质量描述
}
3.2 小时预报
interface HourlyItem {
time: string // 时间
temp: number // 温度
icon: string // 图标
}
3.3 日预报
interface DailyItem {
day: string // 星期
date: string // 日期
icon: string // 图标
high: number // 最高温
low: number // 最低温
desc: string // 描述
}
3.4 模拟数据
这个项目用的是模拟数据,我准备了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: '良' },
// ... 其他城市
]
四、状态怎么管理?
ArkUI用 @State 装饰器声明状态变量,变量变了界面自动更新。
我定义了这些状态:
@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 // AQI
@State currentAQIDesc: string = '良' // AQI描述
@State showCityPicker: boolean = false // 显示城市选择器?
@State hourlyData: HourlyItem[] = [] // 小时预报
@State dailyData: DailyItem[] = [] // 日预报
变量有点多,但都是必要的。
五、核心功能
5.1 根据天气换背景
这个功能我觉得挺酷的,天气不同,背景颜色也跟着变:
private getBgGradient(cond: string): string {
if (cond === '晴') return '#FF9F0A' // 橙色
if (cond === '多云' || cond === '阴') return '#8E8E93' // 灰色
if (cond === '小雨' || cond === '阵雨' || cond === '雷阵雨') return '#5AC8FA' // 蓝色
return '#4A90D9'
}
private getBgEnd(cond: string): string {
if (cond === '晴') return '#FFD60A' // 亮黄
if (cond === '多云' || cond === '阴') return '#636366'
if (cond === '小雨' || cond === '阵雨' || cond === '雷阵雨') return '#007AFF'
return '#87CEEB'
}
然后在Column上用渐变:
Column() {
// 内容...
}
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
[this.getBgGradient(this.currentCondition), 0],
[this.getBgEnd(this.currentCondition), 1]
]
})
5.2 天气图标
我用emoji代替图标,简单省事:
private getWeatherEmoji(cond: string): string {
if (cond === '晴') return '☀️'
if (cond === '多云') return '⛅'
if (cond === '阴') return '☁️'
if (cond === '小雨') return '🌦️'
if (cond === '阵雨') return '🌧️'
if (cond === '雷阵雨') return '⛈️'
return '🌤️'
}
5.3 切换城市
点击城市名,弹出选择器,选了就切换:
private switchCity(city: string): void {
const data = this.getWeatherByCity(city)
this.location = city
this.currentTemp = data.temp
this.currentCondition = data.cond
// ... 更新其他状态
this.hourlyData = this.generateHourlyData(data.cond, data.temp)
this.dailyData = this.generateDailyData(data.cond, data.high, data.low)
this.showCityPicker = false
}
六、UI组件封装
ArkUI有个 @Builder 装饰器,可以封装可复用的UI组件。
6.1 信息卡片
显示空气质量、紫外线那些小卡片:
@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)
}
用的时候:
Row() {
this.compactCard('💨 空气质量', String(this.currentAQI), this.currentAQIDesc, '#34C759')
this.compactCard('☀️ 紫外线', this.currentUV, '', '#FF9F0A')
this.compactCard('💧 湿度', String(this.currentHumidity) + '%', '', '#5AC8FA')
this.compactCard('🌬️ 风速', this.currentWind, '', '#8E8E93')
}
6.2 小时预报项
@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)
}
6.3 日预报行
这个带温度条,稍微复杂点:
@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)
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 })
}
温度条宽度和颜色是动态计算的:
private tempBarWidth(low: number, high: number): string {
return Math.floor(((high - low) / 20) * 100 + 20) + '%'
}
private tempBarColor(low: number, high: number): string {
const avg = (low + high) / 2
if (avg >= 28) return '#FF3B30' // 红色
if (avg >= 20) return '#FF9F0A' // 橙色
return '#34C759' // 绿色
}
6.4 城市选择按钮
@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) })
}
七、页面布局
7.1 整体结构
build() {
Stack() {
// 主内容
Scroll() {
Column() {
// 1. 顶部天气区(带渐变背景)
// 2. 信息卡片
// 3. 小时预报
// 4. 7天预报
// 5. 更新时间
}
}
// 城市选择弹窗
if (this.showCityPicker) {
Column() {
// 弹窗内容
}
}
}
}
用 Stack 是为了实现弹窗叠加效果。
7.2 顶部天气区
Column() {
// 城市按钮
Row() {
Blank()
Button(this.location + ' ▾')
.fontSize(18)
.fontColor(Color.White)
.backgroundColor(Color.Transparent)
.onClick(() => { this.showCityPicker = true })
Blank()
}
.width('100%')
.padding({ top: 30 })
// 天气图标
Text(this.getWeatherEmoji(this.currentCondition))
.fontSize(64)
.margin({ top: 8 })
// 温度
Text(String(this.currentTemp) + '°')
.fontSize(80)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
// 天气状况
Text(this.currentCondition)
.fontSize(20)
.fontColor('#FFFFFF')
.opacity(0.9)
// 最高最低
Text('最高 ' + this.currentHigh + '° 最低 ' + this.currentLow + '°')
.fontSize(15)
.fontColor('#FFFFFF')
.opacity(0.7)
.margin({ top: 6 })
}
.width('100%')
.padding({ bottom: 28 })
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
[this.getBgGradient(this.currentCondition), 0],
[this.getBgEnd(this.currentCondition), 1]
]
})
7.3 城市选择弹窗
if (this.showCityPicker) {
Column() {
Column() {
Text('选择城市')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.margin({ top: 20, bottom: 16 })
Row() {
this.cityButton('北京市')
this.cityButton('上海市')
this.cityButton('广州市')
this.cityButton('深圳市')
}
Row() {
this.cityButton('杭州市')
this.cityButton('成都市')
this.cityButton('武汉市')
this.cityButton('南京市')
}
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 })
}
八、运行效果
在DevEco Studio里点运行,效果如下:

切换城市:

不同天气的背景:




九、踩坑记录
9.1 渐变背景不显示
一开始我忘了加 linearGradient,背景就是纯色。后来发现要在Column上加。
9.2 弹窗点内部也会关闭
一开始弹窗的遮罩层onClick写在最外层,结果点弹窗内部也会关闭。后来把内部弹窗容器的onClick留空,问题解决。
9.3 温度条宽度计算不对
一开始用固定宽度,后来改成根据温差计算,效果好多了。
十、学到了啥
- 状态管理 -
@State驱动UI更新 - 组件封装 -
@Builder复用UI - 渐变背景 -
linearGradient - 条件渲染 -
if控制显示 - 叠加布局 -
Stack实现弹窗 - 动态样式 - 方法返回颜色、宽度
十一、后续可以做的
- 接真实天气API
- 加定位功能
- 加天气预警
- 加下拉刷新
- 加天气动画(下雨效果之类的)
总结
这个天气App虽然用的是模拟数据,但UI和交互都做完了,整体效果还不错。ArkUI写起来确实挺舒服的,声明式UI果然香。
有问题欢迎评论区交流!
觉得有用的话,点个赞再走呗~ 👍
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)