项目名称
浅草天气是一款个人独立开发,设计的 鸿蒙 APP,开发过程中主要参考了 HUAWEI 天气,使用的开发工具有 DevEco studio 和 Figma,在开发的过程中经历过多次改版优化,最终实现了如下的 Feature
- 定位
- 当前天气
- 天气指数
- 每小时天气
- 多日天气预报
运行条件
列出运行该项目所必须的条件和相关依赖
- API 11
- deploy retrofit
技术使用
- 使用装饰器来简化权限请求
- 网络请求
- list 列表渲染,grid 列表渲染
- state,provide-consume 使用
- 组件的提取
- 自定义view 绘制
- 贝塞尔曲线
- 迁移 ts-retrofit 到 Harmony OS
技术讲解
定位权限管理
考虑到天气对地理位置要求不高,本 App 默认是不需要请求定位权限的,目前的技术方案是从高德地图的 Web Api 里面通过IP来获取地址。当然如果需要精确的位置可以先获取 GPS 权限,然后进行请求。 详见本人前段时间的文章
网络请求
目前使用鸿蒙自带的 http + Promise 来实现的,通过 gpt 去生成对应的 class,使用 JSON 来解析对象,具体实现可以参考以下代码
getHourWeather(): Promise<DayForecast> {
return new Promise(async (resolve: Function, reject: Function) => {
let url = `${WeatherConstants.SERVER}/${WeatherConstants.HOUR_FORECAST}`;
if (this.address.infocode == null) {
this.address = await this.getCityFromIp();
}
let location = this.address
const result = calculateAverageCoordinates(location.rectangle);
url += '?location=' + result.averageLongitude.toFixed(2) + "," + result.averageLatitude.toFixed(2) + '&key=' + WeatherConstants.KEY;
let httpRequest = http.createHttp();
httpRequest.request(url, (err, data) => {
if (!err) {
let day : DayForecast = JSON.parse(data.result as string)
resolve(day)
}
else {
reject(err)
console.log("getHourWeather:", err.message);
}
});
});
}
天气的 Api 是来自 和风天气 ,可以去上面申请免费的 Api 来使用,免费的额度有限制,也可以花个一杯奶茶钱用付费版的,次数管饱。
页面布局
首先展示的是当前的天气,看天气自然最关系当天的天气咯,其次展示当天的天气指数,天气指数里面我选取了几个比较重要的指数,当然大家也可以选择自己感兴趣的指数。后面展示的是逐小时天气预报,这个是用一个横向的 list 实现的。最后就是一个最重要的多日预报,是一个自定义绘制的 View,也是本篇要重点讲的内容。
多日预报实现
如何绘制这样一个多日天气布局呢,首先我们了解一下 鸿蒙绘制的 Api ,在这里我们需要使用三个 Api。
- fillText(text: string, x: number, y: number, maxWidth?: number): void; 用来在指定位置绘制文字
- drawImage(image: ImageBitmap | PixelMap, dx: number, dy: number, dw: number, dh: number): void; 在指定位置绘制图像
- bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; 绘三阶制贝塞尔曲线
fillText
参数比较简单,相信大家一眼就懂了,但是这里面有一点比较重要,就是我们在绘制文字包括图片的时候要跟当前天气坐标对齐,也就是我们习惯上看起来的上下居中对齐,为了达到这一点我们一般需要测量一下文字的宽度,然后沿着 X 轴平移一半的宽度。下面是示例代码
const w = this.context.measureText(temp).width
this.context.fillText(temp, lowPs[i][0] - (w >> 1), lowPs[i][1] + 20, 40)
drawImage
这个 Api 对我来说有点奇怪,因为我没找到可以对图片进行 tint 的参数,感觉还是挺麻烦的,项目里面图标颜色都是我在 Figma 上面自己改的,如果有好的方案希望有知道的同学告知一下。
bezierCurveTo
绘制三阶贝塞尔曲线,里面的参数我需要介绍一下,首先呢 cp 就是 control point 翻译过来就是控制点,主要是用来决定曲线走向的,最后一个点是 end point,一般是我们实际用到的数据。在很多图像曲线展示中为了使得展示效果比较自然平滑,我们都会采用贝塞尔曲线绘制。
在绘制过程中我们会使用一些算法来生成贝塞尔曲线上面的点,这样我们就可以按照 Api 进行绘制曲线。在这里面随便多嘴一句,写代码是一项具有挑战的工作,但是我们不能等所有的工作都准备好采取行动,很多时候我们需要边学边用。
好了,回归正题,项目里面的贝塞尔曲线的点就是用 github 上搜出来的算法计算出来的,亲测还挺好用。需要注意的是计算出来的点是不包括初始点的,很好理解第一个点不是任何点的 end point,而且他是一个始发点,所以在实现过程中我们需要 moveTo 第一点,至于剩下的坐标我们只需要调用bezierCurveTo 即可。下面是实现的示例代码
const x = highPs[0][0];
const y = highPs[0][1];
this.context.moveTo(x, y)
let points = curveToBezier(highPs)
for (let i = 1; i < points.length - 2; i += 3) {
const controlPoint1 = points[i];
const controlPoint2 = points[i + 1];
const endPoint = points[i + 2];
this.context.bezierCurveTo(
controlPoint1[0], controlPoint1[1],
controlPoint2[0], controlPoint2[1],
endPoint[0], endPoint[1]
);
}
如何计算贝塞尔曲线的坐标点?
一般来说我们绘制的时候一个 view 的高度是固定的,上面绘制什么,下面绘制什么都已经确定了。那么中间的坐标位置就是 marginTop+otherViewsHeight(minY) 到 marginTop+otherViewsHeight+curveHeight(maxY) 至于 x 轴的位置,则没有影响跟每日坐标保持一致即可,至于每日的坐标,大家可以按照自己的要求或者其他考虑,有一个合理的 x 轴的 interval 即可。
那么我们知道 y 轴上的 max 以及 min 之后,如何确定每个点在 y 轴上距离呢,比如有 10 个点 [10,11,13,20,30,32,23,32,23,20], 这些点要能全部展示在 view 里面不会跑出来,或者显示的不会留下很多空白,那么我们首先计算 max 和 min ,用这两个点标定最低位和最高位,这样的话温度曲线展示的就会比较自然。下面是示意代码:
for (let index = 0; index < highs.length; index++) {
const element = highs[index];
let y = this.calY(max - parseFloat(element), unit, remainSpace) + curTop // margin top
let x = index * perGap + curLeft // margin left
highPs.push([x, y])
}
结尾
由于项目里面用到的是免费的 Api 可能不稳定,如果你遇到请求错误的情况可以自己花个十块钱买一个付费 key ,由于本人精力,技术有限代码中难免存在一些问题,如果有问题可以留言或者提 pr。最后附上代码传送门 周末愉快。
作者:CaptainZ
链接:https://juejin.cn/post/7401060368087777318
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。