[HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)
[HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
天气应用是移动设备上常见的应用类型,它需要在有限的屏幕空间内展示多种天气信息,如当前温度、天气状况、未来天气预报、空气质量等。HarmonyOS NEXT提供的GridRow和GridCol组件非常适合实现这种复杂的信息展示布局。本教程将详细讲解如何使用这些组件构建一个美观、实用的天气应用界面。
2. 数据结构设计
在实现天气应用界面之前,我们需要先定义相关的数据结构。在本案例中,我们定义了以下几个接口:
2.1 当前天气信息
interface CurrentWeather {
temperature: number; // 当前温度
weatherType: string; // 天气类型
weatherIcon: ResourceStr; // 天气图标
location: string; // 位置
updateTime: string; // 更新时间
}
2.2 天气详情信息
interface WeatherDetail {
title: string; // 标题
value: string; // 值
unit: string; // 单位
icon: ResourceStr; // 图标
}
2.3 每日天气预报
interface DailyForecast {
date: string; // 日期
day: string; // 星期几
weatherType: string; // 天气类型
weatherIcon: ResourceStr; // 天气图标
highTemp: number; // 最高温度
lowTemp: number; // 最低温度
}
2.4 每小时天气预报
interface HourlyForecast {
time: string; // 时间
weatherIcon: ResourceStr; // 天气图标
temperature: number; // 温度
}
3. 数据准备
在组件内部,我们准备了模拟的天气数据:
// 当前天气信息
private currentWeather: CurrentWeather = {
temperature: 26,
weatherType: '晴',
weatherIcon: $r("app.media.sunny"),
location: '北京市海淀区',
updateTime: '10:30 更新'
};
// 天气详情信息
private weatherDetails: WeatherDetail[] = [
{ title: '体感温度', value: '28', unit: '°C', icon: $r("app.media.temperature") },
{ title: '湿度', value: '45', unit: '%', icon: $r("app.media.humidity") },
{ title: '气压', value: '1013', unit: 'hPa', icon: $r("app.media.pressure") },
{ title: '能见度', value: '25', unit: 'km', icon: $r("app.media.visibility") },
{ title: '风速', value: '3.5', unit: 'm/s', icon: $r("app.media.wind") },
{ title: '紫外线', value: '中等', unit: '', icon: $r("app.media.uv") }
];
// 每日天气预报
private dailyForecasts: DailyForecast[] = [
{ date: '6月1日', day: '今天', weatherType: '晴', weatherIcon: $r("app.media.sunny"), highTemp: 28, lowTemp: 18 },
{ date: '6月2日', day: '明天', weatherType: '多云', weatherIcon: $r("app.media.cloudy"), highTemp: 26, lowTemp: 17 },
{ date: '6月3日', day: '周五', weatherType: '小雨', weatherIcon: $r("app.media.rainy"), highTemp: 24, lowTemp: 16 },
{ date: '6月4日', day: '周六', weatherType: '阴', weatherIcon: $r("app.media.overcast"), highTemp: 25, lowTemp: 17 },
{ date: '6月5日', day: '周日', weatherType: '晴', weatherIcon: $r("app.media.sunny"), highTemp: 29, lowTemp: 19 }
];
// 每小时天气预报
private hourlyForecasts: HourlyForecast[] = [
{ time: '现在', weatherIcon: $r("app.media.sunny"), temperature: 26 },
{ time: '11:00', weatherIcon: $r("app.media.sunny"), temperature: 27 },
{ time: '12:00', weatherIcon: $r("app.media.sunny"), temperature: 28 },
{ time: '13:00', weatherIcon: $r("app.media.cloudy"), temperature: 28 },
{ time: '14:00', weatherIcon: $r("app.media.cloudy"), temperature: 27 },
{ time: '15:00', weatherIcon: $r("app.media.cloudy"), temperature: 26 },
{ time: '16:00', weatherIcon: $r("app.media.cloudy"), temperature: 25 },
{ time: '17:00', weatherIcon: $r("app.media.cloudy"), temperature: 24 }
];
4. 布局实现
4.1 整体布局结构
天气应用界面的整体结构如下:
Scroll() {
Column() {
// 当前天气信息
this.CurrentWeatherSection()
// 每小时天气预报
this.HourlyForecastSection()
// 每日天气预报
this.DailyForecastSection()
// 天气详情信息
this.WeatherDetailsSection()
}
.width('100%')
.padding(16)
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
整体布局使用了Scroll组件作为容器,使内容可以垂直滚动。内部使用Column组件垂直排列各个部分,包括当前天气信息、每小时天气预报、每日天气预报和天气详情信息。最外层的Scroll设置了100%宽高、隐藏滚动条和浅灰色背景。
4.2 当前天气信息部分
当前天气信息部分的实现如下:
@Builder
private CurrentWeatherSection() {
Column() {
Row() {
Text(this.currentWeather.location)
.fontSize(16)
.fontColor('#333333')
Text(this.currentWeather.updateTime)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row() {
Text(this.currentWeather.temperature.toString())
.fontSize(64)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text('°C')
.fontSize(24)
.fontColor('#333333')
.margin({ top: 12 })
}
.margin({ top: 16 })
Row() {
Image(this.currentWeather.weatherIcon)
.width(24)
.height(24)
Text(this.currentWeather.weatherType)
.fontSize(16)
.fontColor('#333333')
.margin({ left: 4 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
}
当前天气信息部分使用Column组件垂直排列以下元素:
- 位置和更新时间:使用Row组件水平排列,两端对齐
- 温度:使用Row组件水平排列温度数值和单位
- 天气类型:使用Row组件水平排列天气图标和天气类型文本
4.3 每小时天气预报部分
每小时天气预报部分的实现如下:
@Builder
private HourlyForecastSection() {
Column() {
Text('24小时预报')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
Scroll() {
Row() {
ForEach(this.hourlyForecasts, (item: HourlyForecast) => {
Column() {
Text(item.time)
.fontSize(12)
.fontColor('#333333')
Image(item.weatherIcon)
.width(24)
.height(24)
.margin({ top: 8, bottom: 8 })
Text(item.temperature.toString() + '°')
.fontSize(14)
.fontColor('#333333')
}
.width(64)
.alignItems(HorizontalAlign.Center)
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16 })
}
每小时天气预报部分使用Column组件垂直排列以下元素:
- 标题:使用Text组件显示"24小时预报",设置16的字体大小、粗体字重和左对齐
- 滚动区域:使用Scroll组件,内部使用Row组件水平排列每小时天气信息
- 每小时天气信息:使用Column组件垂直排列时间、天气图标和温度
4.4 每日天气预报部分
每日天气预报部分的实现如下:
@Builder
private DailyForecastSection() {
Column() {
Text('未来5天')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
ForEach(this.dailyForecasts, (item: DailyForecast) => {
Row() {
Text(item.date)
.fontSize(14)
.fontColor('#333333')
.width('25%')
Text(item.day)
.fontSize(14)
.fontColor('#333333')
.width('15%')
Row() {
Image(item.weatherIcon)
.width(24)
.height(24)
Text(item.weatherType)
.fontSize(14)
.fontColor('#333333')
.margin({ left: 4 })
}
.width('30%')
Row() {
Text(item.lowTemp.toString() + '°')
.fontSize(14)
.fontColor('#666666')
Text(' / ')
.fontSize(14)
.fontColor('#666666')
Text(item.highTemp.toString() + '°')
.fontSize(14)
.fontColor('#333333')
}
.width('30%')
.justifyContent(FlexAlign.End)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.justifyContent(FlexAlign.SpaceBetween)
.border({ width: { bottom: 1 }, color: '#F0F0F0', style: BorderStyle.Solid })
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16 })
}
每日天气预报部分使用Column组件垂直排列以下元素:
- 标题:使用Text组件显示"未来5天",设置16的字体大小、粗体字重和左对齐
- 每日天气信息:使用ForEach循环遍历每日天气数据,每项使用Row组件水平排列日期、星期几、天气类型和温度范围
4.5 天气详情信息部分
天气详情信息部分的实现如下:
@Builder
private WeatherDetailsSection() {
Column() {
Text('详细信息')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
GridRow({ columns: 3, gutter: 16 }) {
ForEach(this.weatherDetails, (item: WeatherDetail) => {
GridCol({ span: 1 }) {
Column() {
Row() {
Image(item.icon)
.width(16)
.height(16)
Text(item.title)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 4 })
}
.width('100%')
Row() {
Text(item.value)
.fontSize(16)
.fontColor('#333333')
Text(item.unit)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 2 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(12)
.backgroundColor('#F9F9F9')
.borderRadius(12)
}
})
}
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16, bottom: 16 })
}
天气详情信息部分使用Column组件垂直排列以下元素:
- 标题:使用Text组件显示"详细信息",设置16的字体大小、粗体字重和左对齐
- 网格布局:使用GridRow和GridCol组件实现3列网格布局,每个网格项显示一个天气详情信息
4.6 GridRow和GridCol配置
在天气详情信息部分,我们使用了GridRow和GridCol组件实现网格布局:
GridRow({ columns: 3, gutter: 16 })
参数说明:
columns: 3
:设置网格为3列布局gutter: 16
:设置网格项之间的间距为16vp
GridCol({ span: 1 })
参数说明:
span: 1
:设置该列占用1个网格单元
5. 布局效果分析
5.1 整体布局特点
本案例中的天气应用界面具有以下特点:
特点 | 描述 |
---|---|
布局方式 | 垂直滚动的卡片式布局 |
背景色 | #F5F5F5(浅灰色) |
卡片背景色 | 白色 |
卡片圆角 | 16vp |
卡片间距 | 16vp |
卡片内边距 | 16vp |
5.2 天气详情网格布局特点
天气详情部分的网格布局具有以下特点:
特点 | 描述 |
---|---|
列数 | 3列 |
间距 | 16vp |
网格项背景色 | #F9F9F9(浅灰色) |
网格项圆角 | 12vp |
网格项内边距 | 12vp |
6. GridRow和GridCol组件详解
6.1 GridRow组件
GridRow组件是HarmonyOS NEXT中实现网格布局的容器组件,它将子组件按照网格进行排列。GridRow组件的主要属性如下:
属性名 | 类型 | 描述 |
---|---|---|
columns | number | GridRowColumnOptions | 设置布局列数 |
gutter | Length | GutterOption | 设置列间距 |
breakpoints | { value: Array, reference: BreakpointsReference } | 设置断点响应参考值 |
direction | GridRowDirection | 设置布局方向 |
其中,GridRowColumnOptions类型可以设置不同断点下的列数,GutterOption类型可以设置水平和垂直方向的间距。
6.2 GridCol组件
GridCol组件用于定义网格中的列项,它必须作为GridRow的子组件使用。GridCol组件的主要属性如下:
属性名 | 类型 | 描述 |
---|---|---|
span | number | GridColSpanOption | 设置跨列数 |
offset | number | GridColOffsetOption | 设置偏移列数 |
order | number | GridColOrderOption | 设置显示顺序 |
其中,GridColSpanOption、GridColOffsetOption和GridColOrderOption类型可以设置不同断点下的跨列数、偏移列数和显示顺序。
7. 完整代码
以下是天气应用网格布局的完整代码:
// 当前天气信息接口
interface CurrentWeather {
temperature: number; // 当前温度
weatherType: string; // 天气类型
weatherIcon: ResourceStr; // 天气图标
location: string; // 位置
updateTime: string; // 更新时间
}
// 天气详情信息接口
interface WeatherDetail {
title: string; // 标题
value: string; // 值
unit: string; // 单位
icon: ResourceStr; // 图标
}
// 每日天气预报接口
interface DailyForecast {
date: string; // 日期
day: string; // 星期几
weatherType: string; // 天气类型
weatherIcon: ResourceStr; // 天气图标
highTemp: number; // 最高温度
lowTemp: number; // 最低温度
}
// 每小时天气预报接口
interface HourlyForecast {
time: string; // 时间
weatherIcon: ResourceStr; // 天气图标
temperature: number; // 温度
}
@Component
export struct WeatherGrid {
// 当前天气信息
private currentWeather: CurrentWeather = {
temperature: 26,
weatherType: '晴',
weatherIcon: $r("app.media.sunny"),
location: '北京市海淀区',
updateTime: '10:30 更新'
};
// 天气详情信息
private weatherDetails: WeatherDetail[] = [
{ title: '体感温度', value: '28', unit: '°C', icon: $r("app.media.temperature") },
{ title: '湿度', value: '45', unit: '%', icon: $r("app.media.humidity") },
{ title: '气压', value: '1013', unit: 'hPa', icon: $r("app.media.pressure") },
{ title: '能见度', value: '25', unit: 'km', icon: $r("app.media.visibility") },
{ title: '风速', value: '3.5', unit: 'm/s', icon: $r("app.media.wind") },
{ title: '紫外线', value: '中等', unit: '', icon: $r("app.media.uv") }
];
// 每日天气预报
private dailyForecasts: DailyForecast[] = [
{ date: '6月1日', day: '今天', weatherType: '晴', weatherIcon: $r("app.media.sunny"), highTemp: 28, lowTemp: 18 },
{ date: '6月2日', day: '明天', weatherType: '多云', weatherIcon: $r("app.media.cloudy"), highTemp: 26, lowTemp: 17 },
{ date: '6月3日', day: '周五', weatherType: '小雨', weatherIcon: $r("app.media.rainy"), highTemp: 24, lowTemp: 16 },
{ date: '6月4日', day: '周六', weatherType: '阴', weatherIcon: $r("app.media.overcast"), highTemp: 25, lowTemp: 17 },
{ date: '6月5日', day: '周日', weatherType: '晴', weatherIcon: $r("app.media.sunny"), highTemp: 29, lowTemp: 19 }
];
// 每小时天气预报
private hourlyForecasts: HourlyForecast[] = [
{ time: '现在', weatherIcon: $r("app.media.sunny"), temperature: 26 },
{ time: '11:00', weatherIcon: $r("app.media.sunny"), temperature: 27 },
{ time: '12:00', weatherIcon: $r("app.media.sunny"), temperature: 28 },
{ time: '13:00', weatherIcon: $r("app.media.cloudy"), temperature: 28 },
{ time: '14:00', weatherIcon: $r("app.media.cloudy"), temperature: 27 },
{ time: '15:00', weatherIcon: $r("app.media.cloudy"), temperature: 26 },
{ time: '16:00', weatherIcon: $r("app.media.cloudy"), temperature: 25 },
{ time: '17:00', weatherIcon: $r("app.media.cloudy"), temperature: 24 }
];
build() {
Scroll() {
Column() {
// 当前天气信息
this.CurrentWeatherSection()
// 每小时天气预报
this.HourlyForecastSection()
// 每日天气预报
this.DailyForecastSection()
// 天气详情信息
this.WeatherDetailsSection()
}
.width('100%')
.padding(16)
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder
private CurrentWeatherSection() {
Column() {
Row() {
Text(this.currentWeather.location)
.fontSize(16)
.fontColor('#333333')
Text(this.currentWeather.updateTime)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Row() {
Text(this.currentWeather.temperature.toString())
.fontSize(64)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text('°C')
.fontSize(24)
.fontColor('#333333')
.margin({ top: 12 })
}
.margin({ top: 16 })
Row() {
Image(this.currentWeather.weatherIcon)
.width(24)
.height(24)
Text(this.currentWeather.weatherType)
.fontSize(16)
.fontColor('#333333')
.margin({ left: 4 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
}
@Builder
private HourlyForecastSection() {
Column() {
Text('24小时预报')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
Scroll() {
Row() {
ForEach(this.hourlyForecasts, (item: HourlyForecast) => {
Column() {
Text(item.time)
.fontSize(12)
.fontColor('#333333')
Image(item.weatherIcon)
.width(24)
.height(24)
.margin({ top: 8, bottom: 8 })
Text(item.temperature.toString() + '°')
.fontSize(14)
.fontColor('#333333')
}
.width(64)
.alignItems(HorizontalAlign.Center)
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16 })
}
@Builder
private DailyForecastSection() {
Column() {
Text('未来5天')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
ForEach(this.dailyForecasts, (item: DailyForecast) => {
Row() {
Text(item.date)
.fontSize(14)
.fontColor('#333333')
.width('25%')
Text(item.day)
.fontSize(14)
.fontColor('#333333')
.width('15%')
Row() {
Image(item.weatherIcon)
.width(24)
.height(24)
Text(item.weatherType)
.fontSize(14)
.fontColor('#333333')
.margin({ left: 4 })
}
.width('30%')
Row() {
Text(item.lowTemp.toString() + '°')
.fontSize(14)
.fontColor('#666666')
Text(' / ')
.fontSize(14)
.fontColor('#666666')
Text(item.highTemp.toString() + '°')
.fontSize(14)
.fontColor('#333333')
}
.width('30%')
.justifyContent(FlexAlign.End)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.justifyContent(FlexAlign.SpaceBetween)
.border({ width: { bottom: 1 }, color: '#F0F0F0', style: BorderStyle.Solid })
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16 })
}
@Builder
private WeatherDetailsSection() {
Column() {
Text('详细信息')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
GridRow({ columns: 3, gutter: 16 }) {
ForEach(this.weatherDetails, (item: WeatherDetail) => {
GridCol({ span: 1 }) {
Column() {
Row() {
Image(item.icon)
.width(16)
.height(16)
Text(item.title)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 4 })
}
.width('100%')
Row() {
Text(item.value)
.fontSize(16)
.fontColor('#333333')
Text(item.unit)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 2 })
}
.margin({ top: 8 })
}
.width('100%')
.padding(12)
.backgroundColor('#F9F9F9')
.borderRadius(12)
}
})
}
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ top: 16, bottom: 16 })
}
}
8. 与其他布局方式的比较
8.1 GridRow vs List布局
HarmonyOS NEXT提供了多种布局方式,除了GridRow和GridCol组件,还有List组件。下面是两者在天气详情信息展示中的比较:
特性 | GridRow + GridCol | List |
---|---|---|
布局方式 | 网格布局 | 列表布局 |
适用场景 | 多列展示,如天气详情信息 | 单列展示,如每日天气预报 |
响应式支持 | 原生支持断点响应 | 需要额外处理 |
间距设置 | 统一设置gutter | 需要单独为每个子项设置margin |
跨列能力 | 支持span属性设置跨列 | 不支持 |
8.2 适用场景
GridRow和GridCol组件在天气应用中适用于以下场景:
- 天气详情信息的多列展示
- 需要在不同屏幕尺寸下调整列数的响应式布局
- 需要某些元素跨列的复杂布局
List组件在天气应用中适用于以下场景:
- 每日天气预报的单列展示
- 需要支持滚动的长列表
- 需要支持列表项的增删改查操作
9. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的GridRow和GridCol组件实现天气应用的网格布局。通过合理的数据结构设计和布局配置,我们实现了一个美观、实用的天气应用界面,包括当前天气信息、每小时天气预报、每日天气预报和天气详情信息。在下一篇教程中,我们将继续深入探讨如何优化这个布局,并添加更多交互功能和高级特性。
- 0回答
- 3粉丝
- 0关注
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(下)
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(上)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(上)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(上)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(上)
- [HarmonyOS NEXT 实战案例八] 电影票务网格布局(上)
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(下)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(下)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(下)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(下)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(下)
- [HarmonyOS NEXT 实战案例八] 电影票务网格布局(下)
- [HarmonyOS NEXT 实战案例三] 音乐专辑网格展示(上)
- [HarmonyOS NEXT 实战案例二] 新闻资讯网格列表(上)