[HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)

2025-06-06 22:44:30
103次阅读
0个评论

[HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

img_73aa8767.png

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组件垂直排列以下元素:

  1. 位置和更新时间:使用Row组件水平排列,两端对齐
  2. 温度:使用Row组件水平排列温度数值和单位
  3. 天气类型:使用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组件垂直排列以下元素:

  1. 标题:使用Text组件显示"24小时预报",设置16的字体大小、粗体字重和左对齐
  2. 滚动区域:使用Scroll组件,内部使用Row组件水平排列每小时天气信息
  3. 每小时天气信息:使用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组件垂直排列以下元素:

  1. 标题:使用Text组件显示"未来5天",设置16的字体大小、粗体字重和左对齐
  2. 每日天气信息:使用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组件垂直排列以下元素:

  1. 标题:使用Text组件显示"详细信息",设置16的字体大小、粗体字重和左对齐
  2. 网格布局:使用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组件实现天气应用的网格布局。通过合理的数据结构设计和布局配置,我们实现了一个美观、实用的天气应用界面,包括当前天气信息、每小时天气预报、每日天气预报和天气详情信息。在下一篇教程中,我们将继续深入探讨如何优化这个布局,并添加更多交互功能和高级特性。

收藏00

登录 后评论。没有帐号? 注册 一个。