从零开始纯血鸿蒙天气预报-主界面(4)

116 阅读3分钟

易得天气

完成了紫外线、湿度、体感温度、风向、日出日落、气压、可见度、未来40日天气面板UI

效果图

2025-03-20 15.46.33.gif

页面布局

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,
    }
  }
}

从零开始纯血鸿蒙天气预报-闪屏页

从零开始纯血鸿蒙天气预报-页面跳转以及网络请求

从零开始纯血鸿蒙天气预报-MVVM

从零开始纯血鸿蒙天气预报-数据库(storm)

从零开始纯血鸿蒙天气预报-城市选择页面

从零开始纯血鸿蒙天气预报-城市管理页面(1)

从零开始纯血鸿蒙天气预报-城市管理页面(2)

从零开始纯血鸿蒙天气预报-一镜到底效果

从零开始纯血鸿蒙天气预报-主界面(1)

从零开始纯血鸿蒙天气预报-主界面(2)

从零开始纯血鸿蒙天气预报-主界面(3)