《HormonyOSNEXT之天气应用的开发》
本项目是基于HarmonyOSNEXT语言开发的天气预报系统
1、数据的获取
该项目通过高德地图提供的天气预报文件获取天气数据,以下是高德地图提供天气预报的网址。
url: https://lbs.amap.com/api/webservice/guide/api/weatherinfo
通过上述网址可以获得申请个人开发者的权限 得到相对应的key值,再通过key值获取天气预报的JSON文件
url:https://restapi.amap.com/v3/weather/weatherInfo?city=<城市天气代码>&key=<个人开发者获取的key值>&extensions=all
通过上述的网址在每一个<>中填入对应的数据就可以获得城市天气代码所对应的城市的连续四天的天气预报。
如下图
上图是通过高德地图个人开发者key值获取的北京市从今天开始连续四天的天气信息,这是一个JSON文件。
2、数据的读取
在读取上述网络数据之前,需要在Dev IDE中创建与其数据结构对应的数据类型。
通过返回的JSON文件可以看出,该网址返回的数据最里面的一层数据类型是cast,存储的是每天的天气信息;再外一层的数据类型是foercasts,存储的城市信息以及城市对应的天气信息数组;要注意在forecasts的外面还有一层数据类型,可以在图中看出forecasts外面还有一层{},所以需要定义三种数据类型,通过嵌套来读取天气数据。
数据类型的定义
cast数据类型
export interface cast {
date: string
dayWeather: string
nightWeather: string
dayTemp: number
nightTemp: number
dayWind: string
nightwind: string//晚上风向
nightpower: string//晚上风级
dayPower: string
dayTemp_float: number
nightTemp_float: number
}
//天气信息
export class casts {
date: string//日期
dayweather: string//白天天气
nightweather: string//晚上天气
daytemp: number//白天温度
nighttemp: number//晚上温度
daywind: string//白天风向
nightwind: string//晚上风向
nightpower: string//晚上风级
daypower: string//白天风级
daytemp_float: number//白天温度小数
nighttemp_float: number//晚上温度小数
constructor(item: cast) {
this.date = item.date
this.dayweather = item.dayWeather
this.nightweather = item.nightWeather
this.daytemp = item.dayTemp
this.nighttemp = item.nightTemp
this.daywind = item.dayWind
this.nightwind = item.nightwind
this.daypower = item.dayPower
this.nightpower = item.nightpower
this.daytemp_float = item.dayTemp_float
this.nighttemp_float = item.nightTemp_float
}
}
forecasts数据类型
import {casts} from './casts'
//城市信息
export interface forecast {
city: string//城市名
adcode: number//城市代码
province: string//城市
reporttime: string//发布时间
casts: Array<casts>//城市天气信息
}
export class forecasts {
city: string
adcode: number
province: string//城市
reporttime: string//发布时间
casts: Array<casts>
constructor(item: forecast) {
this.city = item.city
this.adcode = item.adcode
this.province = item.province
this.reporttime = item.reporttime
this.casts = item.casts
}
}
最外层的数据类型,我将它命名为WeatherModel(可以不是这个名字)
WeatherModel数据类型
import {forecasts} from './forecasts'
// 包括整个返回数据的数据类型
export interface weather{
status: number
count: number
infoCode: number
forecasts: Array<forecasts>//对应城市信息以及天气信息
}
export class WeatherModel {
status: number
count: number
infoCode: number
forecasts: Array<forecasts> = []
constructor(item: weather) {
this.status = item.status
this.count = item.count
this.infoCode = item.infoCode
this.forecasts = item.forecasts
}
}
注意!!自己所定义的字段的名称一定要与读取数据的文件里面的字段名称相同;否则不会进行读取,而是将其对应字段数据定义为undefined!!
3、通过定义一个工具类,用于进行对网络数据的读取
在这里,我定义了一个getWeatherUtil工具类,通过利用异步函数获取网络数据
getWeatherUtil类
import { reject, result } from '@wolfx/lodash'
import {WeatherModel} from './WeatherModel'
import http from '@ohos.net.http'
// 获取天气的工具类
class getWeatherUtil {
// 发送一个URL请求,返回对应的数据
// 传入一个城市代码,传入城市代码返回对应城市的天气情况
getWeather(cityCode: number) {
// 由于是网络请求 所以可以使用Promise异步获取
return new Promise<WeatherModel>((resolve, reject) => {
// 创建一个获取数据的对象
let request = http.createHttp()
// 需要发送的url数据
let url = `https://restapi.amap.com/v3/weather/weatherInfo?city=${cityCode}&key=<个人开发者的key值>&extensions=all`
//将url传入request中,从而得到该url中的数据
let result = request.request(url)
// 由于使用的是异步获取,所以真正的数据还需要以下操作 res才是最终的数据
result.then((res) => {
// 如果 res 的响应码是200,那么说明这个数据返回成功
if(res.responseCode === 200) {
console.log(res.result.toString());
// 将获取的 数据 返回至 return 再由 return 返回出函数
// 由于读取的是JSON文件,所以通过JSON.parse返回数据
resolve(JSON.parse(res.result.toString()))
}
})
// 如果失败的话,则报异常
.catch((err: Error) => {
console.log(err + '')
})
})
}
// 直接发送多个URL,然后结果一并返回
// 传入城市编码数组
// 联网获取,所以使用异步获取
async getWeathers(cityCodes: Array<number>) {
// 首先定义一个Promise数组,使用 Promise.all 方法 发送多个URL
let promises: Array<Promise<WeatherModel>> = []
// 定义一个WeatherModel数组,将获取的数据全部一并返回
let weatherModels: Array<WeatherModel> = []
// 将传入的参数传入到promises里面
for (let i = 0; i < cityCodes.length; i++) {
promises.push(this.getWeather(cityCodes[i]))
}
// 使用 all 方法
// 这里 result 获取的就是 WeatherModel 集合
await Promise.all(promises)
.then((result) => {
//遍历的每一个 element 元素 都是 WeatherModel 类型
for (const element of result) {
console.log(element.forecasts[0].city);
console.log(element.forecasts[0].casts[0].dayweather)
console.log(element.forecasts[0].casts[0].daytemp + '|' + element.forecasts[0].casts[0].nighttemp);
}
// 让获取的数据传给weatherModels进行返回
weatherModels = result
})
// 返回获取的数据数组
return weatherModels
}
}
// 测试
let get = new getWeatherUtil()
export default get as getWeatherUtil
在上述代码段中,我先定义了一个getWeather函数,用于获取单个城市的城市天气信息;
然后又定义了getWeathers函数,在这个函数中调用了getWeather函数,用于获取多个城市的天气信息。
然后创建了一个工具类的对象get,通过get调用该工具类的getWeather方法和getWeathers方法。
4、在UI界面中初始化获得的网络数据
在主界面中,通过使用生命周期函数中的aboutToAppear函数,使得数据在页面生成时就已经加载完毕
再通过定义一些数组,用于接收读取到的网络数据
代码如下
// 城市代码集合
@State cityCodeList : number[] = [410100]
// 城市名字集合
@State cityNameList: string[] = []
// 城市信息集合
@State cityWeatherList: Array<WeatherModel> = []
// 城市信息的索引
@State cityIndex: number = 0
// 控制生活指数页面是否显示
@State isDisplay: boolean = true
// 控制选择的城市
@State selectedCity: string = ''
// 加载数据
aboutToAppear(): void {
this.initDate()
}
//当页面再次显示的时候获取数据
onPageShow(): void {
console.log('JSON.stringify:' + JSON.stringify(router.getParams()));
if (router.getParams() !== undefined && router.getParams() !== null) {
let params = router.getParams() as string
if (params !== undefined && params !== null) {
// 获取上一个页面传递的数据
this.isDisplay = params['isDisplay']
this.selectedCity = params['city']
// 修改为所选城市的城市代码
this.cityCodeList[0] = getCityCodeByCityName(this.selectedCity)
// 因为更改了城市,所以需要重新获取天气信息
// 首先需要清除原本集合中的数据
this.cityNameList = []
this.cityWeatherList = []
// 数据刷新
this.initDate()
console.log('接收到InterposeShow页面的数据:' + JSON.stringify(params))
}else {
console.log('params赋值失败');
}
}else {
console.log('接受数据失败');
}
}
// 初始化方法
// 由于是获取异步函数的数据,所以这里定义为异步函数
async initDate() {
// 由于调用的是异步方法,所以使用 await 接收
let result:Array<WeatherModel> = await get.getWeathers(this.cityCodeList)
// 对得到的返回结果进行遍历
for (let i = 0; i < result.length; i++) {
// 首先创建一个单独的城市信息和城市名字
let ACityWeather: WeatherModel = result[i]
let ACityName: string = result[i].forecasts[0].city
this.cityNameList.push(ACityName)
// 获取城市信息和城市名字
// 添加到城市信息集合里面
this.cityWeatherList.push(ACityWeather)
}
}
5、UI界面展示
在这里,具体的UI页面代码就不展示,主要展示对于路由跳转页面传递参数的获取和解析的代码逻辑
天气主页面
天气设置页面
天气选择城市页面
在城市选择页面中,通过定义一个数据类型的数组用来存储每个城市的名称以及该城市对应的城市天气代码。再写出一个函数getCityCodeByName(),作用是通过城市的名称返回对应的城市天气代码
给下面的“热门城市”的每个小组件添加onClick()点击事件,当每点击其中的一个城市,利用@State和@Link父子双传修饰符,将点击的城市的城市的名称返回到城市选择页面中,再在定位城市中进行UI渲染。
再通过路由router.back()返回上一层页面的时候,将所在的城市名称作为参数返回到设置页面中
在设置页面中通过获取选择城市页面传递过来的参数对所在城市进行UI渲染
再通过同样的方式,利用路由router.back返回上一层页面的时候,将修改定位后的城市名称作为参数返回到天气主页面中
由于此时主页面是再次显示的状态,所以利用生命周期函数onPageShow(),在页面重新显示的时候利用getCityCodeByName()函数获取修改后的城市所对应的城市代码,同时重新对获取的天气数据进行初始化
//当页面再次显示的时候获取数据
onPageShow(): void {
console.log('JSON.stringify:' + JSON.stringify(router.getParams()));
if (router.getParams() !== undefined && router.getParams() !== null) {
let params = router.getParams() as string
if (params !== undefined && params !== null) {
// 获取上一个页面传递的数据
this.isDisplay = params['isDisplay']
this.selectedCity = params['city']
// 修改为所选城市的城市代码
this.cityCodeList[0] = getCityCodeByCityName(this.selectedCity)
// 因为更改了城市,所以需要重新获取天气信息
// 首先需要清除原本集合中的数据
this.cityNameList = []
this.cityWeatherList = []
// 数据刷新
this.initDate()
console.log('接收到InterposeShow页面的数据:' + JSON.stringify(params))
}else {
console.log('params赋值失败');
}
}else {
console.log('接受数据失败');
}
}
// 初始化方法
// 由于是获取异步函数的数据,所以这里定义为异步函数
async initDate() {
// 由于调用的是异步方法,所以使用 await 接收
let result:Array<WeatherModel> = await get.getWeathers(this.cityCodeList)
// 对得到的返回结果进行遍历
for (let i = 0; i < result.length; i++) {
// 首先创建一个单独的城市信息和城市名字
let ACityWeather: WeatherModel = result[i]
let ACityName: string = result[i].forecasts[0].city
this.cityNameList.push(ACityName)
// 获取城市信息和城市名字
// 添加到城市信息集合里面
this.cityWeatherList.push(ACityWeather)
}
}
值得注意的是,当再次进入设置页面时,若不对所在城市进行初始化,那么所在城市中不会显示城市名称,很显然这是一个bug
在这里,我通过在天气主页面向设置页面跳转的时候,将当前所定位的城市名称作为参数传递到设置页面
在设置页面的生命周期函数aboutToAppear()中利用router.getParams()获取从天气主页面传递过来的数据
// 定义一个函数 返回获取城市信息中“市”之后的字符串
function getString(str: string): string {
let name: string = ''
for (let strElement of str) {
if (strElement !== '市') {
name += strElement
}
}
console.log('getString函数的结果:' + name);
return name
}
// 定义一个函数 如果是SelectCityShow传递的数据则返回true 否则返回false
function getResultByParams(data: string): boolean {
if (data !== undefined) {
return true
}
return false
}
@State isDisplay: boolean = true
// 用于接收所选择的城市名称
@State cityName: string = ''
// // 在页面刚加载的时候进行信息的初始化
aboutToAppear(): void {
// 接收页面路由传递过来的数据
console.log('JSON.stringify:' + JSON.stringify(router.getParams()));
if (router.getParams() !== undefined && router.getParams() !== null) {
let params = router.getParams() as string
if (params !== undefined && params !== null) {
// this.cityName = params['city']
let name: string = params['city']
this.cityName = getString(name)
this.isDisplay = params['isDisplay']
console.log('截获的城市名称:' + this.cityName);
console.log('接收到WeatherBigShowComponent页面的数据:' + JSON.stringify(params))
}else {
console.log('params赋值失败');
}
}else {
console.log('接受数据失败');
}
}
// 当页面再次显示的时候触发
onPageShow(): void {
// 接收页面路由传递过来的数据
console.log('JSON.stringify:' + JSON.stringify(router.getParams()));
if (router.getParams() !== undefined && router.getParams() !== null) {
let params = router.getParams() as string
if (params !== undefined && params !== null) {
// this.cityName = params['info']
let data: string = params['info']
console.log('data:' + data);
// 如果是SelectCityShow传递的数据则进行赋值 否则不赋值
if (getResultByParams(data)) {
this.cityName = data
console.log('接收到SelectCityShow页面的数据:' + JSON.stringify(params))
console.log('this.cityName:' + this.cityName);
}
}else {
console.log('params赋值失败');
}
}else {
console.log('接受数据失败');
}
}
在设置页面中,值得注意的是:当利用router.getParams()获取路由跳转所传递的数据的时候,所有的router.getParams()函数所携带的数据都是一样的。
而在生命周期函数中,都是先执行aboutToAppear(),再执行onPageShow(),所以如果不进行任何处理时,会对接收数据的变量cityName会被赋值两次,但是在onPageShow()中,所传递的参数中并没有info字段,所以解析不出来所对应的数据,因此在onPageShow()中应当进行判断:如果可以解析出info,那么说明是从选择城市页面中传递的数据,可以对cityName进行赋值;所过不可以解析出来,说明是从天气主页面中传递的数据,那么在onPageShow()就不要对cityName进行二次赋值,因为解析不出来对应的数据。
因此,在设置页面中需要注意一下所传递的参数是从哪个页面传递过来的,在进行对应的赋值。
天气应用的源码在上面