前言:本笔记记录于学习HarmonyOS移动应用开发一书中天气预报项目的制作,项目内容因软件更新导致的兼容性问题进行相应的修改,并在修改试错中尝试了与书中不同的方法并在此期间获得收获,故记录此笔记。
目录
书中内容兼容性调整
书中代码是在FA模型下编写的,而现在模型为Stage,需要做修改:
-
config.json配置文件,stage模型中拆分为了app.json和module.json
-
FA模型中deviceConfig配置字段近似于Stage模型的app.json文件
-
Stage模型无需network申请允许网络明文访问,本身就是允许的
和风天气数据的获取
HarmonyOS中实现天气服务功能使用了HTTP数据请求的方式调用天气预报的API。所以可以选择Web API类型的Key,并在鸿蒙应用中使用HTTP客户端库来发起请求
调取数据方法代码:
在调取数据之前需要把存放数据的类写好(相当于准备一个分好格子的容器,将获取到的数据依次存放进去)。
例如
建议把每个小部分分开写,最后再整合到一个大类里,一次调取多项数据需要定义为数组类型
分类好就是这个样子
数据获取方法
//获取实时天气
getTimelyWeather(locationId: string)
{
return new Promise<TimelyWeather>((resolve, reject) => {
let request = http.createHttp()
request.request(`https://devapi.qweather.com/v7/weather/now?location=${locationId}&key=16d8d9c11a9848d1be949b4f440c3611&gzip=n`,
{
method: http.RequestMethod.GET
}
)
.then(res => {
if (res.responseCode === 200) {
console.log('testTag1', res.result)
resolve(JSON.parse(res.result.toString()))
} else {
console.log('查询失败1,', JSON.stringify(res.result))
reject('失败1')
}
})
.catch((err: Error) => {
console.log('查询失败2,', JSON.stringify(err))
reject('失败2')
})
})
}
//获取单个城市实时天气(打包为TimelyWeather类型)
async getTimelyWeather_1(locationId: string): Promise<TimelyWeather>{
const weatherModel = await this.getTimelyWeather(locationId)
console.log(weatherModel.code)
return weatherModel
}
其他数据的获取大体类似,只需要修改网址或者简单修改部分内容即可即可,这里不做过多展示。
学会看日志!!
日志能快速帮助锁定问题的所在,如上述方法中console语句,在日志界面中检索关键字段(如testtag),即可查看接收到数据的情况
这边日志显示成功返回相应数据
关于和风天气api:
WebAPI传回数据均为经过Gzip压缩后的压缩文件,无法使用
解决办法:在网址后加 &gzip=n
,然后正常调用即可
DevEco编译器中,在预览器中预览无法显示数据,而且日志显示也是传回压缩文件,但是在虚拟机中进行运行则能成功显示数据,而且日志也显示是传回JSON格式的数据。虽然不清楚是什么原因但总归是能正常使用了(虽然一直启动虚拟机有点麻烦就是了)
先分后合——制作主页面各部分的天气小组件
主页面整体设计思路是将页面划分为多个组件,后期也方便整理调整组件位置。
其中AllComponent.ets
为所有组件的整合。
利用LIst组件实现滑动显示未来天气
(这个其实可以滑动显示未来多小时的天气,可参考华为内置天气)
天气图标与实时天气的匹配
因为该项目要求实时调用数据,一个一个往里面塞天气图标是不现实的,所以这里使用了函数进行筛选检索。
代码如下:
@Builder
weatherImage(weather:string){
if (weather === '晴') {
Image($r("app.media.tianqi_qing"))
.width(30)
}
if (weather === '阴') {
Image($r("app.media.tianqi_yintian"))
.width(30)
}
if (weather === '多云') {
Image($r("app.media.tianqi_duoyun"))
.width(30)
}
if (weather.includes("雨")) {
Image($r("app.media.tianqi_zhongyu"))
.width(30)
}
}
//根据接受数据的关键信息就可以调取库中的天气图标了
获取实时时间
实时天气的数据中包含了时间,但是格式不相符。为了转化为上图的日期格式,我编写了一个函数。
formatTime(date: Date): string {
// 获取小时数(24小时制)
let hours = date.getHours();
// 转换为12小时制
let period = hours >= 12 ? "下午" : "上午";
hours = hours % 12;
// 如果小时数为0,表示中午12点
hours = hours ? hours : 12;
// 格式化分钟数,始终保留两位数字(例如:"05")
let minutes = date.getMinutes().toString().padStart(2, '0');
// 返回格式化后的时间字符串
return `${period}${hours}:${minutes}`;
}
timeChange(time:string):string{
// 假设的时间字符串
let timeString: string = time
// 使用Date对象解析时间字符串
let date = new Date(timeString);
// 创建一个格式化日期时间的函数
// 调用函数并打印结果
let formattedTime:string = this.formatTime(date);
return formattedTime // 输出: 下午10点00分
}
这样就能把奇奇怪怪的 UTC时间转化为上/下午XX:XX了
组件整体构建
我使用的方法是组件显示的时候开始调取数据,然后将获取数据整合,再在组件中应用。
先上代码吧
import {HourlyWeather} from '../bean/HourlyWeather/HourlyWeather'
import getweatherUtil from '../Util/getWeatherUtil'
import {hourly} from '../bean/HourlyWeather/hourly'
import { EditableTitleBar } from '@ohos.arkui.advanced.EditableTitleBar'
@Component
export struct HourlyWeatherComponent {
private locationId : string = '101010100' //这里预设了一个城市id,后续可以在外部修改
@State hourlyWeather: HourlyWeather = new HourlyWeather()
@State hourly_weather: Array<hourly> = []
aboutToAppear(): void
{
this.initDate()
}
//初始化方法
async initDate(){
let result1: HourlyWeather = await getweatherUtil.getHourlyWeather_1(this.locationId)
this.hourlyWeather = result1
this.hourly_weather = result1.hourly
}
//天气图标函数
@Builder
weatherImage(weather:string,size:number){
if (weather === '晴') {
Image($r("app.media.tianqi_qing"))
.width(size)
}
if (weather === '阴') {
Image($r("app.media.tianqi_yintian"))
.width(size)
}
if (weather === '多云') {
Image($r("app.media.tianqi_duoyun"))
.width(size)
}
if (weather.includes("雨")) {
Image($r("app.media.tianqi_zhongyu"))
.width(size)
}
}
//日期转化函数
formatTime(date: Date): string {
// 获取小时数(24小时制)
let hours = date.getHours();
// 转换为12小时制
let period = hours >= 12 ? "下午" : "上午";
hours = hours % 12;
// 如果小时数为0,表示中午12点
hours = hours ? hours : 12;
// 格式化分钟数,始终保留两位数字(例如:"05")
let minutes = date.getMinutes().toString().padStart(2, '0');
// 返回格式化后的时间字符串
return `${period}${hours}:${minutes}`;
}
timeChange(time:string):string{
// 假设的时间字符串
let timeString: string = time
// 使用Date对象解析时间字符串
let date = new Date(timeString);
// 创建一个格式化日期时间的函数
// 调用函数并打印结果
let formattedTime:string = this.formatTime(date);
return formattedTime // 输出: 下午10点00分
}
build() {
Column(){
List({space:3}){ //使用foreach遍历数据数组
ForEach(this.hourly_weather,(hour:hourly)=>{
ListItem(){
Column({space:10}){
Text(this.timeChange(hour.fxTime))
.fontSize(13)
.fontColor(Color.White)
.fontWeight(FontWeight.Normal)
this.weatherImage(hour.text,30)
Text(hour.temp+"°C")
.fontColor(Color.White)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.padding({top:15,bottom:15,left:15,right:15})
}
})
}
.listDirection(Axis.Horizontal)
.width("92%")
}.width("95%")
.height("15%")
.backgroundColor('rgba(255, 255, 255, 0.1)')
.borderRadius(20)
.margin({top:10})
}
}
然后个人喜好对间隔大小透明度进行微调就可以了
透明组件
.opacity()
只能整体调节组件的透明度,无法调整单个部分的透明度!!
想要只调整背景颜色,可以在.background()
颜色设置里选择rgba格式颜色设置,即.background(rgba(255,255,255,0.1))
,最后一位数就是透明度,越低越透明
效果如图所示(还是这张图)
(其实这个组件是半透明背景)
Gauge组件画图
用这个来画空气质量aqi环形视图
直接上代码吧,其中Gauge组件的相关配置使用详情请见华为官方文档。
import {TimelyAir} from '../bean/TimelyAir/TimelyAir'
import getweatherUtil from '../Util/getWeatherUtil'
@Component
export struct AqiCircleComponent {
private locationId: string = '101010100'
@State timelyAir: TimelyAir = new TimelyAir()
@State aqi: number = Number(this.timelyAir.now.aqi)
aboutToAppear(): void {
this.initDate()
}
//初始化方法
async initDate() {
let result1: TimelyAir = await getweatherUtil.getTimelyAir_1(this.locationId)
this.timelyAir = result1
this.aqi = Number(this.timelyAir.now.aqi)
console.log("aqi_test"+ this.aqi);
}
build() {
Column(){
Stack({alignContent:Alignment.Center}){
Gauge({value:this.aqi,min:0,max:500})
.startAngle(210)
.endAngle(150)
.colors([[0X00FF00, 0.1],[0xFFFF00,0.1],[0xFF6100,0.1],[0xFF0000,0.1],[0xA020F0,0.2],[0x8B0000,0.4]])
.strokeWidth(5)
.description(null)
.trackShadow(null)
Column(){
Text(this.timelyAir.now.category)
.fontSize(35)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
Text(this.timelyAir.now.aqi)
.fontSize(20)
.fontColor(Color.White)
}
}
}
}
}
这段代码仅呈现为图片左边圆形组件,其余部分在另一组件内完成。
使用Canvas组件来绘制多日天气气温曲线
之前发过一次,这里就不放了(见上一篇)
Scroll容器组件实现小组件的拼接展示
以以上组件为参考,制作其他组件
这个就简单用Text组件排版就行
List组件,每小时天气预报
Canvas画图实现未来每日天气曲线
Gauge组件实现,剩余Text排版,实现空气质量实时监测
List组件实现各项指数
制作完全部组件后使用Scroll组件进行整合,一个简易的主页面就做好了。
Scroll 作为可滚动的容器类组件,它最多包含一个子组件,当子组件的布局尺寸在指定的滚动方向上超过 Scroll 的视图窗口时,子组件可以滚动, Scroll 滚动方向只支持水平滚动和竖直滚动。 Scroller 作为滚动组件的控制器,它可以控制滚动组件的一些行为,比如滚动到特定位置,滚动到边界等。
注意!Scroll下的子组件不能设置宽度和高度,不然滑动不了
在该项目中使用Scroll组件实现各天气小组件的整合,在主页面及未来可能需要的页面中实现可滑动的天气信息查看。