易得天气
完成了紫外线、湿度、体感温度、风向、日出日落、气压、可见度、未来40日天气面板UI
效果图
页面布局
Grid() {
ForEach(this.weatherItemData?.itemTypeObserves, (item: number) => {
if (item == Constants.ITEM_TYPE_OBSERVE_UV) {
WeatherObserveUvPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_SHI_DU) {
WeatherObserveShiDuPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_TI_GAN) {
WeatherObserveTiGanPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_WD) {
WeatherObserveWdPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_SUNRISE_SUNSET) {
WeatherObserveSunriseSunsetPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_PRESSURE) {
WeatherObservePressurePanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_VISIBILITY) {
WeatherObserveVisibilityPanel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
} else if (item == Constants.ITEM_TYPE_OBSERVE_FORECAST40) {
WeatherObserveForecase40Panel({
weatherItemData: this.weatherItemData,
isDark: this.isDark,
panelOpacity: this.panelOpacity
})
}
}, (item: number) => item.toString())
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.maxCount(2)
.layoutDirection(GridDirection.Row)
紫外线面板
WeatherObserveUvChart({
panelWidth: 72,
panelHeight: 72,
uvIndex: this.uvIndex(),
uvIndexMax: this.uvIndexMax()
})
@ComponentV2
export struct WeatherObserveUvChart {
@Param @Require panelWidth: number
@Param @Require panelHeight: number
@Param uvIndex: number = 0
@Param uvIndexMax: number = 0
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Stack() {
Canvas(this.canvasContext)
.width(this.panelWidth)
.height(this.panelHeight)
.onReady(() => {
this.draw()
})
Text(this.uvIndex.toString())
.fontSize(18)
.fontColor($r('app.color.special_white'))
.fontWeight(FontWeight.Bold)
Text('uv')
.width(this.panelWidth)
.height(this.panelHeight)
.textAlign(TextAlign.Center)
.align(Alignment.Bottom)
.padding({ bottom: 5 })
.fontSize(11)
.fontColor($r('app.color.special_white'))
}
.width(this.panelWidth)
.height(this.panelHeight)
}
draw() {
const canvas = this.canvasContext
if (!canvas) {
return
}
const lineWidth = 4
const centerX = this.panelWidth / 2
const centerY = this.panelHeight / 2
const radius = this.panelWidth / 2 - lineWidth
canvas.beginPath()
canvas.arc(centerX, centerY, radius, Math.PI * 0.75, Math.PI * 0.25)
let grad = canvas.createConicGradient(0, centerX, centerY)
grad.addColorStop(0.125, '#BB10DE')
grad.addColorStop(0.25, '#BB10DE')
grad.addColorStop(0.375, '#37CA00')
grad.addColorStop(0.45, '#37CA00')
grad.addColorStop(0.625, '#FFDE00')
grad.addColorStop(0.75, '#FE5B21')
grad.addColorStop(0.925, '#FC0B23')
grad.addColorStop(1.0, '#BB10DE')
canvas.strokeStyle = grad
canvas.lineWidth = lineWidth
canvas.lineCap = 'round'
canvas.stroke()
const percent = fixPercent(this.uvIndex / this.uvIndexMax)
const radian = Math.PI * 0.75 + Math.PI * (2.25 - 0.75) * percent
const x = centerX + radius * Math.cos(radian)
const y = centerY + radius * Math.sin(radian)
canvas.beginPath()
canvas.arc(x, y, 7, 0, Math.PI * 2)
canvas.globalCompositeOperation = 'destination-out'
canvas.fill()
let color = '#37CA00'
if (percent > 0.8) {
color = '#BB10DE'
} else if (percent > 0.6) {
color = '#FC0B23'
} else if (percent > 0.4) {
color = '#FE5B21'
} else if (percent > 0.2) {
color = '#FFDE00'
}
canvas.beginPath()
canvas.arc(x, y, lineWidth, 0, Math.PI * 2)
canvas.globalCompositeOperation = 'source-over'
canvas.fillStyle = color
canvas.fill()
}
}
风向面板
WeatherObserveWdChart({
panelWidth: 72,
panelHeight: 72,
wd: this.weatherItemData?.weatherData?.observe?.wd
})
@ComponentV2
export struct WeatherObserveWdChart {
@Param @Require panelWidth: number
@Param @Require panelHeight: number
@Param wd: string = ''
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Stack() {
Canvas(this.canvasContext)
.width(this.panelWidth)
.height(this.panelHeight)
.onReady(() => {
this.draw()
})
Stack() {
Image($r('app.media.ic_wd_icon'))
.width(12)
.height(12)
.colorFilter(ColorUtils.translateColor($r('app.color.special_white')))
}
.width(24)
.height(24)
.borderRadius(100)
.backgroundColor($r('app.color.color_0da8ff'))
Text('北')
.width(this.panelWidth)
.height(this.panelHeight)
.textAlign(TextAlign.Center)
.align(Alignment.Top)
.padding({ top: 8 })
.fontSize(11)
.fontColor($r('app.color.special_white'))
.fontWeight(FontWeight.Bold)
Text('南')
.width(this.panelWidth)
.height(this.panelHeight)
.textAlign(TextAlign.Center)
.align(Alignment.Bottom)
.padding({ bottom: 8 })
.fontSize(11)
.fontColor($r('app.color.special_white'))
.fontWeight(FontWeight.Bold)
Text('西')
.width(this.panelWidth)
.height(this.panelHeight)
.textAlign(TextAlign.Start)
.align(Alignment.Center)
.padding({ left: 8 })
.fontSize(11)
.fontColor($r('app.color.special_white'))
.fontWeight(FontWeight.Bold)
Text('东')
.width(this.panelWidth)
.height(this.panelHeight)
.textAlign(TextAlign.End)
.align(Alignment.Center)
.padding({ right: 8 })
.fontSize(11)
.fontColor($r('app.color.special_white'))
.fontWeight(FontWeight.Bold)
}
.width(this.panelWidth)
.height(this.panelHeight)
}
draw() {
const canvas = this.canvasContext
if (!canvas) {
return
}
const centerX = this.panelWidth / 2
const centerY = this.panelHeight / 2
const length = 6
canvas.lineWidth = 1
canvas.save()
canvas.translate(centerX, centerY)
for (let index = 0; index < 72; index++) {
canvas.beginPath()
canvas.moveTo(centerX - length, 0)
canvas.lineTo(centerX, 0)
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'),
index == 0 || index == 18 || index == 36 || index == 54 ? 1 : 0.4)
canvas.stroke()
canvas.rotate(2 * Math.PI / 72)
}
canvas.restore()
canvas.save()
canvas.translate(centerX, centerY)
const wd = this.wd
if (wd == "南风") {
canvas.rotate(Math.PI * 0.5)
} else if (wd == "西风") {
canvas.rotate(Math.PI)
} else if (wd == "北风") {
canvas.rotate(Math.PI * 1.5)
} else if (wd == "东南风") {
canvas.rotate(Math.PI * 0.25)
} else if (wd == "西南风") {
canvas.rotate(Math.PI * 0.75)
} else if (wd == "西北风") {
canvas.rotate(Math.PI * 1.25)
} else if (wd == "东北风") {
canvas.rotate(Math.PI * 1.75)
}
canvas.beginPath()
canvas.moveTo(centerX - length, -0.8)
canvas.lineTo(-centerX + length + 3, -0.8)
canvas.lineTo(-centerX + length + 3 + 3, -0.8 - 3)
canvas.lineTo(-centerX, 0)
canvas.lineTo(-centerX + length + 3 + 3, 0.8 + 3)
canvas.lineTo(-centerX + length + 3, 0.8)
canvas.lineTo(centerX - length, 0.8)
canvas.fillStyle = ColorUtils.alpha($r('app.color.color_0da8ff'), 1)
canvas.fill()
canvas.beginPath()
canvas.arc(centerX - length, 0, 4, 0, Math.PI * 2)
canvas.fillStyle = ColorUtils.alpha($r('app.color.color_0da8ff'), 1)
canvas.fill()
canvas.beginPath()
canvas.arc(centerX - length, 0, 2, 0, Math.PI * 2)
canvas.fillStyle = ColorUtils.alpha($r('app.color.special_white'), 1)
canvas.fill()
canvas.restore()
}
}
日出日落面板
Column() {
WeatherObserveSunriseSunsetChart({
panelWidth: 72,
panelHeight: 42,
date: this.currentWeatherDetailData?.date,
sunrise: this.currentWeatherDetailData?.sunrise,
sunset: this.currentWeatherDetailData?.sunset
})
Blank().height(4)
Row() {
Text(this.currentWeatherDetailData?.sunrise)
.fontSize(10)
.fontColor($r('app.color.special_white'))
Text(this.currentWeatherDetailData?.sunset)
.fontSize(10)
.fontColor($r('app.color.special_white'))
}
.width(72)
.justifyContent(FlexAlign.SpaceBetween)
}
@ComponentV2
export struct WeatherObserveSunriseSunsetChart {
@Param @Require panelWidth: number
@Param @Require panelHeight: number
@Param date: string = ''
@Param sunrise: string = ''
@Param sunset: string = ''
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Canvas(this.canvasContext)
.width(this.panelWidth)
.height(this.panelHeight)
.onReady(() => {
this.draw()
})
}
draw() {
const canvas = this.canvasContext
if (!canvas) {
return
}
const centerX = this.panelWidth / 2
const lineWidth = 4
const left = lineWidth
const top = lineWidth
const right = this.panelWidth - lineWidth
const bottom = this.panelHeight - lineWidth
const width = right - left
const height = bottom - top
canvas.save()
const lineY = height * 0.8
canvas.moveTo(0, lineY)
canvas.lineTo(right, lineY)
canvas.lineWidth = 0.5
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'), 0.4)
canvas.stroke()
canvas.restore()
canvas.save()
canvas.rect(0, 0, this.panelWidth, lineY)
canvas.clip()
canvas.beginPath()
canvas.lineWidth = lineWidth
canvas.moveTo(left, bottom)
const p1 = { x: left + width / 4 + width / 4 / 2, y: bottom } as Position
const p2 = { x: left + width / 4 - width / 4 / 2, y: top } as Position
canvas.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, centerX, top)
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'), 1)
canvas.lineCap = 'round'
canvas.stroke()
const p3 = { x: right - width / 4 + width / 4 / 2, y: top } as Position
const p4 = { x: right - width / 4 - width / 4 / 2, y: bottom } as Position
canvas.bezierCurveTo(p3.x, p3.y, p4.x, p4.y, right, bottom)
canvas.stroke()
canvas.restore()
canvas.save()
canvas.rect(0, lineY, this.panelWidth, this.panelHeight)
canvas.clip()
canvas.beginPath()
canvas.lineWidth = lineWidth
canvas.moveTo(left, bottom)
canvas.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, centerX, top)
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'), 0.4)
canvas.lineCap = 'round'
canvas.stroke()
canvas.bezierCurveTo(p3.x, p3.y, p4.x, p4.y, right, bottom)
canvas.stroke()
canvas.clip()
canvas.restore()
const currentMill = DateUtil.getToday().getTime()
const sunriseSunsetMill = this.sunriseSunsetMill
const sunriseMill = sunriseSunsetMill[0]
const sunsetMill = sunriseSunsetMill[1]
let percent = 0
if (currentMill < sunriseMill) {
const dateTime = getHarmonyDateTimeFormattedString(`${this.date}0000`)
const mill = DateUtil.getFormatDate(dateTime).getTime()
percent = (currentMill - mill) / (sunriseMill - mill) * 0.2
} else if (currentMill < sunsetMill) {
percent = 0.2 + (currentMill - sunriseMill) / (sunsetMill - sunriseMill) * 0.6
} else {
const dateTime = getHarmonyDateTimeFormattedString(`${this.date}2400`)
const mill = DateUtil.getFormatDate(dateTime).getTime()
percent = 0.2 + 0.6 + (currentMill - sunsetMill) / (mill - sunsetMill) * 0.2
}
percent = fixPercent(percent)
let h: Position
if (percent < 0.5) {
h = this.getBezier3Point({ x: left, y: bottom }, p1, p2, { x: centerX, y: lineWidth }, percent / 0.5)
} else {
h = this.getBezier3Point({ x: centerX, y: lineWidth }, p3, p4, { x: right, y: bottom }, (percent - 0.5) / 0.5)
}
canvas.beginPath()
canvas.arc(h.x, h.y, 7, 0, Math.PI * 2)
canvas.globalCompositeOperation = 'destination-out'
canvas.fill()
canvas.beginPath()
canvas.arc(h.x, h.y, 4, 0, Math.PI * 2)
canvas.globalCompositeOperation = 'source-over'
canvas.fillStyle = ColorUtils.alpha($r('app.color.special_white'), percent < 0.2 || percent > 0.8 ? 0.4 : 1)
canvas.fill()
}
get sunriseSunsetMill() {
const sunriseDateTime =
getHarmonyDateTimeFormattedString(`${this.date}${this.sunrise}`.replaceAll(':', ''))
const sunsetDateTime =
getHarmonyDateTimeFormattedString(`${this.date}${this.sunset}`.replaceAll(':', ''))
const sunriseMill = DateUtil.getFormatDate(sunriseDateTime).getTime()
const sunsetMill = DateUtil.getFormatDate(sunsetDateTime).getTime()
return [sunriseMill, sunsetMill]
}
getBezier3Point(p1: Position, cp1: Position, cp2: Position, p2: Position, t: number): Position {
const a = this.lerp(p1, cp1, t)
const b = this.lerp(cp1, cp2, t)
const c = this.lerp(cp2, p2, t)
const e = this.lerp(a, b, t)
const f = this.lerp(b, c, t)
return this.lerp(e, f, t)
}
lerp(p1: Position, p2: Position, t: number): Position {
return {
x: (1 - t) * p1.x + t * p2.x,
y: (1 - t) * p1.y + t * p2.y,
}
}
}