易得天气
完成了15日天气预报面板UI
效果图
1、曲线/列表切换
2、列表展开/收起
页面布局
Column() {
Divider()
.strokeWidth(0.5)
.color(ColorUtils.alpha($r('app.color.special_white'), 0.2))
if (this.currentDailyWeatherType == Constants.LINE_CHART_DAILY_WEATHER) {
// 曲线
this.lineChartDailyWeather()
} else if (this.currentDailyWeatherType == Constants.LIST_DAILY_WEATHER) {
// 列表
this.listDailyWeather()
}
}
.padding({ top: Constants.ITEM_STICKY_HEIGHT })
.clipShape(new PathShape({ commands: this.panelPathCommands }))
曲线/列表切换动画
通过transition实现曲线/列表切换动画
.transition(TransitionEffect.opacity(0).animation({ curve: Curve.Ease, duration: 300 }))
展开/收起动画
通过animation实现展开/收起动画
.height(this.panelHeight)
.animation({ curve: Curve.Ease, duration: 300 })
get panelHeight(): number {
if (this.currentDailyWeatherType == Constants.LINE_CHART_DAILY_WEATHER) {
const find = this.weatherItemData?.weatherData?.forecast15?.find(it => StrUtil.isNotEmpty(it.aqi_level_name))
return find != null ? 420 : 382
}
const value = this.isExpand ? Math.max(Constants.DAILY_WEATHER_ITEM_COUNT,
this.weatherItemData?.weatherData?.forecast15?.length ?? Constants.DAILY_WEATHER_ITEM_COUNT) :
Math.min(Constants.DAILY_WEATHER_ITEM_COUNT,
this.weatherItemData?.weatherData?.forecast15?.length ?? Constants.DAILY_WEATHER_ITEM_COUNT)
return Constants.ITEM_STICKY_HEIGHT + Constants.DAILY_WEATHER_ITEM_HEIGHT * value +
Constants.DAILY_WEATHER_BOTTOM_HEIGHT
}
列表状态下的气温条
WeatherTempLineBar({
barWidth: '100%',
barHeight: 4,
temp: this.isToday(item) ? this.weatherItemData?.weatherData?.observe?.temp : undefined,
high: item?.high,
low: item?.low,
maxTemp: this.maxTempData()?.high,
minTemp: this.minTempData()?.low
})
.layoutWeight(1)
@ComponentV2
export struct WeatherTempLineBar {
@Param @Require barWidth: Length = 0
@Param @Require barHeight: number = 0
@Param temp?: number = undefined
@Param high: number = 0
@Param low: number = 0
@Param maxTemp: number = 0
@Param minTemp: number = 0
@Local colors: Array<[ResourceColor, number]> = []
@Local pathCommands: string = ''
@Local todayTempMarginLeft: number = 0
build() {
Stack() {
Stack({ alignContent: Alignment.Start }) {
if (this.temp) {
Circle({ width: this.barHeight, height: this.barHeight })
.fill($r('app.color.special_white'))
.margin({ left: this.todayTempMarginLeft })
}
}
.width('100%')
.height(this.barHeight)
.linearGradient({
direction: GradientDirection.Right,
colors: this.colors
})
.clipShape(new PathShape({ commands: this.pathCommands }))
}
.width(this.barWidth)
.height(this.barHeight)
.backgroundColor(ColorUtils.alpha($r('app.color.special_black'), 0.05))
.borderRadius(100)
.clip(true)
.onSizeChange((_, newValue) => {
const width = newValue.width
if (width) {
const widthValue = width as number
const tempBarWidth =
Math.max(((this.high - this.low) / (this.maxTemp - this.minTemp)) * widthValue, this.barHeight)
const marginLeft = ((this.low - this.minTemp) / (this.maxTemp - this.minTemp)) * widthValue
let todayTempMarginLeft = 0
if (this.temp) {
todayTempMarginLeft = (this.temp - this.minTemp) / (this.maxTemp - this.minTemp) * widthValue
if (todayTempMarginLeft < 0) {
todayTempMarginLeft = 0
}
if (todayTempMarginLeft > widthValue - this.barHeight) {
todayTempMarginLeft = widthValue - this.barHeight
}
if (todayTempMarginLeft < marginLeft) {
todayTempMarginLeft = marginLeft
}
if (todayTempMarginLeft > marginLeft + tempBarWidth - this.barHeight) {
todayTempMarginLeft = marginLeft + tempBarWidth - this.barHeight
}
}
const colors: Array<[ResourceColor, number]> = []
let startStop = 0
if (this.minTemp <= 0) {
startStop += (0 - this.minTemp) / (this.maxTemp - this.minTemp)
colors.push([$r('app.color.color_55dffc'), startStop])
}
if (this.maxTemp > 0 && this.minTemp <= 15) {
startStop += (Math.min(this.maxTemp, 15) - Math.max(this.minTemp, 0)) / (this.maxTemp - this.minTemp)
colors.push([$r('app.color.color_eade6f'), startStop])
}
if (this.maxTemp > 15 && this.minTemp <= 30) {
startStop +=
(Math.min(this.maxTemp, 30) - Math.max(this.minTemp, 15)) / (this.maxTemp - this.minTemp)
colors.push([$r('app.color.color_feba4f'), startStop])
}
if (this.maxTemp > 30) {
colors.push([$r('app.color.color_ff6f55'), 1])
}
this.colors = colors
const barHeightPx = vp2px(this.barHeight)
const marginLeftPx = vp2px(marginLeft)
const tempBarWidthPx = vp2px(tempBarWidth)
const radius = barHeightPx / 2
this.pathCommands =
`M ${marginLeftPx + radius} 0 Q ${marginLeftPx} ${radius}, ${marginLeftPx +
radius} ${barHeightPx} H ${marginLeftPx + tempBarWidthPx -
radius} Q ${marginLeftPx + tempBarWidthPx} ${radius}, ${marginLeftPx + tempBarWidthPx - radius} 0 Z`
this.todayTempMarginLeft = todayTempMarginLeft
}
})
}
}
曲线状态下的气温曲线
WeatherDailyTempPanel({
panelWidth: 68.5,
panelHeight: 128,
preData: this.weatherItemData?.weatherData?.forecast15?.[index - 1],
data: item,
nextData: this.weatherItemData?.weatherData?.forecast15?.[index + 1],
maxTemp: this.maxTempData()?.high,
minTemp: this.minTempData()?.low
})
@ComponentV2
export struct WeatherDailyTempPanel {
@Param @Require panelWidth: number
@Param @Require panelHeight: number
@Param preData?: WeatherDetailData = undefined
@Param data?: WeatherDetailData = undefined
@Param nextData?: WeatherDetailData = undefined
@Param maxTemp: number = 0
@Param minTemp: number = 0
private topAndBottomGaps: number = 32
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
}
this.drawStartArea(canvas, true)
this.drawStartArea(canvas, false)
this.drawCenterArea(canvas, true)
this.drawCenterArea(canvas, false)
this.drawEndArea(canvas, true)
this.drawEndArea(canvas, false)
}
drawStartArea(canvas: CanvasRenderingContext2D, isHigh: boolean) {
if (!this.preData && this.data && this.nextData) {
const centerX = this.panelWidth / 2
const tempYAxis = this.calTempYAxis((isHigh ? this.data.high : this.data.low) ?? 0)
const nextTempYAxis = this.calTempYAxis((isHigh ? this.nextData.high : this.nextData.low) ?? 0)
canvas.lineWidth = 1
canvas.beginPath()
canvas.moveTo(centerX, tempYAxis)
canvas.quadraticCurveTo(centerX + this.panelWidth / 4, (nextTempYAxis - tempYAxis) / 4 + tempYAxis,
this.panelWidth, (nextTempYAxis - tempYAxis) / 2 + tempYAxis)
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'), 0.3)
canvas.stroke()
canvas.beginPath()
canvas.arc(centerX, tempYAxis, 2.5, 0, Math.PI * 2)
canvas.fillStyle = ColorUtils.alpha($r('app.color.special_white'), 0.3)
canvas.fill()
this.drawTemp(canvas, getTemp(isHigh ? this.data.high : this.data.low),
ColorUtils.alpha($r('app.color.special_white'), 0.3), tempYAxis, isHigh)
}
}
drawCenterArea(canvas: CanvasRenderingContext2D, isHigh: boolean) {
if (this.preData && this.data && this.nextData) {
const centerX = this.panelWidth / 2
const dateIsToday = DateUtil.isToday(getHarmonyDateTimeFormattedString(this.data.date))
const dateIsBefore = isBefore(this.data.date)
const tempYAxis = this.calTempYAxis((isHigh ? this.data.high : this.data.low) ?? 0)
const preTempYAxis = this.calTempYAxis((isHigh ? this.preData.high : this.preData.low) ?? 0)
const nextTempYAxis = this.calTempYAxis((isHigh ? this.nextData.high : this.nextData.low) ?? 0)
const moveToY = (preTempYAxis - tempYAxis) / 2 + tempYAxis
const p1 = { x: this.panelWidth / 4, y: (moveToY - tempYAxis) / 2 + tempYAxis } as Position
const p2 =
{ x: this.panelWidth - this.panelWidth / 4, y: (nextTempYAxis - tempYAxis) / 4 + tempYAxis } as Position
if (dateIsToday || dateIsBefore) {
canvas.save()
canvas.rect(this.panelWidth / 2, 0, this.panelWidth / 2, this.panelHeight)
canvas.clip()
this.drawCenterLine(canvas, moveToY, p1, p2, tempYAxis, nextTempYAxis,
ColorUtils.alpha($r('app.color.special_white'), 1))
canvas.restore()
canvas.save()
canvas.rect(0, 0, this.panelWidth / 2, this.panelHeight)
canvas.clip()
this.drawCenterLine(canvas, moveToY, p1, p2, tempYAxis, nextTempYAxis,
ColorUtils.alpha($r('app.color.special_white'), 0.3))
canvas.restore()
} else {
this.drawCenterLine(canvas, moveToY, p1, p2, tempYAxis, nextTempYAxis,
ColorUtils.alpha($r('app.color.special_white'), 1))
}
canvas.beginPath()
canvas.arc(centerX, (p2.y - p1.y) / 2 + p1.y, 2.5, 0, Math.PI * 2)
canvas.fillStyle = ColorUtils.alpha($r('app.color.special_white'), dateIsBefore ? 0.3 : 1)
canvas.fill()
this.drawTemp(canvas, getTemp(isHigh ? this.data.high : this.data.low),
ColorUtils.alpha($r('app.color.special_white'), dateIsBefore ? 0.3 : 1), (p2.y - p1.y) / 2 + p1.y, isHigh)
}
}
drawCenterLine(canvas: CanvasRenderingContext2D, moveToY: number, p1: Position, p2: Position,
tempYAxis: number, nextTempYAxis: number, lineColor: number) {
if (this.preData && this.data && this.nextData) {
canvas.lineWidth = 1
canvas.beginPath()
canvas.moveTo(0, moveToY)
canvas.bezierCurveTo(p1.x, p2.y, p2.x, p2.y, this.panelWidth, (nextTempYAxis - tempYAxis) / 2 + tempYAxis)
canvas.strokeStyle = lineColor
canvas.stroke()
}
}
drawEndArea(canvas: CanvasRenderingContext2D, isHigh: boolean) {
if (this.preData && this.data && !this.nextData) {
const centerX = this.panelWidth / 2
const tempYAxis = this.calTempYAxis((isHigh ? this.data.high : this.data.low) ?? 0)
const preTempYAxis = this.calTempYAxis((isHigh ? this.preData.high : this.preData.low) ?? 0)
canvas.lineWidth = 1
canvas.beginPath()
canvas.moveTo(centerX, tempYAxis)
canvas.quadraticCurveTo(centerX - this.panelWidth / 4, (preTempYAxis - tempYAxis) / 4 + tempYAxis,
0, (preTempYAxis - tempYAxis) / 2 + tempYAxis)
canvas.strokeStyle = ColorUtils.alpha($r('app.color.special_white'), 1)
canvas.stroke()
canvas.beginPath()
canvas.arc(centerX, tempYAxis, 2.5, 0, Math.PI * 2)
canvas.fillStyle = ColorUtils.alpha($r('app.color.special_white'), 1)
canvas.fill()
this.drawTemp(canvas, getTemp(isHigh ? this.data.high : this.data.low),
ColorUtils.alpha($r('app.color.special_white'), 1), tempYAxis, isHigh)
}
}
drawTemp(canvas: CanvasRenderingContext2D, text: string, textColor: number, tempYAxis: number, isHigh: boolean) {
const centerX = this.panelWidth / 2
canvas.textAlign = 'center'
canvas.font = '13vp sans-serif'
canvas.fillStyle = textColor
canvas.textBaseline = isHigh ? 'bottom' : 'top'
canvas.fillText(text, centerX, isHigh ? tempYAxis - 4 : tempYAxis + 4)
}
calTempYAxis(temp: number): number {
const diff = this.maxTemp - temp
const diffTemp = this.maxTemp - this.minTemp
const percent = diff / diffTemp
return (this.panelHeight - 2 * this.topAndBottomGaps) * percent + this.topAndBottomGaps
}
}