从零开始纯血鸿蒙天气预报-天气弹窗(1)

易得天气

1、极端天气详情天气弹窗

2、空气质量详情天气弹窗

3、空气质量指数信息弹窗

使用到第三方库:

perl 复制代码
"@pura/harmony-dialog": "^1.1.4",

效果图

极端天气详情天气弹窗

kotlin 复制代码
.onClick(() => {
  if (this.showHideWeatherContent) {
    this.showHideWeatherContent(false)
  }
  DialogHelper.showCustomDialog(wrapBuilder(WeatherAlarmsDetailPopupBuilder), {
    dialogId: 'weather_alarms_detail_popup',
    alarms: this.weatherItemData?.weatherData?.alarms,
    isDark: this.isDark,
    panelOpacity: this.panelOpacity,
    ref: this.alarmsDetailPopupRef,
    backCancel: false,
    maskColor: $r('app.color.transparent'),
    transition: AnimationHelper.transitionInDown(0),
    onWillDismiss: () => {
      this.alarmsDetailPopupRef.exit()
    },
    onDidDisappear: () => {
      if (this.showHideWeatherContent) {
        this.showHideWeatherContent(true)
      }
    }
  } as WeatherAlarmsDetailPopupOptions)
})
less 复制代码
@Builder
export function WeatherAlarmsDetailPopupBuilder(options: WeatherAlarmsDetailPopupOptions) {
  WeatherAlarmsDetailPopup({ options: options })
}

@ComponentV2
export struct WeatherAlarmsDetailPopup {
  @Require @Param options: WeatherAlarmsDetailPopupOptions
  @Local animHeight: number = Constants.WEATHER_ALARM_PANEL_HEIGHT
  @Local contentOpacity: number = 0
  @Local marginTop: number = 0
  @Local alignment: FlexAlign = FlexAlign.Start
  private heights: Array<number> = []

  aboutToAppear(): void {
    this.options.ref.exit = this.exit
    const weatherAlarmsPanelRectangle = componentUtils.getRectangleById('weather_alarms_panel')
    this.marginTop = px2vp(weatherAlarmsPanelRectangle.windowOffset.y)
    setTimeout(() => {
      this.contentOpacity = 1
      this.marginTop = 0
      this.alignment = FlexAlign.Center
    }, 16)
  }

  exit = () => {
    const weatherAlarmsPanelRectangle = componentUtils.getRectangleById('weather_alarms_panel')
    this.alignment = FlexAlign.Start
    this.marginTop = px2vp(weatherAlarmsPanelRectangle.windowOffset.y)
    this.contentOpacity = 0
    setTimeout(() => {
      DialogHelper.closeDialog('weather_alarms_detail_popup')
    }, 200)
  }

  build() {
    Column() {
      Stack() {
        Swiper() {
          ForEach(this.options.alarms, (item: WeatherAlarmsData, index: number) => {
            this.alarmItem(item, index)
          }, (item: WeatherAlarmsData) => item.short_title)
        }
        .width('100%')
        .height(this.animHeight)
        .animation({ duration: 200, curve: Curve.Linear })
        .loop(false)
        .indicator(
          (this.options.alarms?.length ?? 0) <= 1 ? false :
          Indicator.dot()
            .itemWidth(3)
            .itemHeight(2)
            .selectedItemWidth(6)
            .selectedItemHeight(2)
            .color(ColorUtils.alpha($r('app.color.special_white'), 0.5))
            .selectedColor($r('app.color.special_white'))
        )
        .onChange((index) => {
          this.animHeight = this.heights[index]
        })
      }
      .width(`calc(100% - ${2 * 16}vp)`)
      .opacity(this.contentOpacity)
      .margin({ top: this.marginTop })
      .animation({ curve: Curve.Ease, duration: 200 })
      .backgroundColor(ColorUtils.alpha(this.options.isDark ? $r('app.color.special_white') :
      $r('app.color.special_black'),
        this.options.panelOpacity))
      .borderRadius(Constants.ITEM_PANEL_RADIUS)
    }
    .width('100%')
    .height('100%')
    .justifyContent(this.alignment)
    .animation({ curve: Curve.Ease, duration: 200 })
    .onClick(() => {
      this.exit()
    })
  }

  @Builder
  alarmItem(item: WeatherAlarmsData, index: number) {
    Column() {
      Blank().height(12)
      Text(item.short_title)
        .fontSize(16)
        .fontColor($r('app.color.special_white'))
        .fontWeight(FontWeight.Bold)
      Blank().height(6)
      Text(this.pubTimeDesc(item))
        .fontSize(13)
        .fontColor($r('app.color.special_white'))
      Blank().height(8)
      Text(item.desc)
        .fontSize(14)
        .fontColor($r('app.color.special_white'))
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .onSizeChange((_, newValue) => {
          const height = newValue.height
          if (height) {
            const heightValue = height as number
            if (!this.heights[index]) {
              this.heights.splice(index, 0, heightValue + 53 + 42)
              if (this.animHeight == Constants.WEATHER_ALARM_PANEL_HEIGHT) {
                this.animHeight = this.heights[0]
              }
            }
          }
        })
      Blank().height(28)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .padding({ left: 16, right: 16 })
  }

  pubTimeDesc(item: WeatherAlarmsData): string {
    let pubTimeDesc = ''
    const date = DateUtil.getFormatDate(item.pub_time)
    const diff = DateUtil.getToday().getTime() - date.getTime()
    const fewHours = Math.floor(diff / 1000 / 60 / 60)
    pubTimeDesc = `${fewHours}小时前更新`
    if (fewHours <= 0) {
      const fewMinutes = Math.floor(diff / 1000 / 60)
      pubTimeDesc = `${fewMinutes}分钟前更新`
      if (fewMinutes <= 0) {
        const fewMills = Math.floor(diff / 1000)
        pubTimeDesc = `${fewMills}秒前更新`
      }
    }
    return pubTimeDesc
  }
}

空气质量详情天气弹窗

kotlin 复制代码
.onClick(() => {
  if (this.showHideWeatherContent) {
    this.showHideWeatherContent(false)
  }
  DialogHelper.showCustomDialog(wrapBuilder(AirQualityDetailPopupBuilder), {
    dialogId: 'air_quality_detail_popup',
    evn: this.weatherItemData?.weatherData?.evn,
    isDark: this.isDark,
    panelOpacity: this.panelOpacity,
    ref: this.popupExitRef,
    backCancel: false,
    maskColor: $r('app.color.transparent'),
    transition: AnimationHelper.transitionInDown(0),
    onWillDismiss: () => {
      this.popupExitRef.exit()
    },
    onDidDisappear: () => {
      if (this.showHideWeatherContent) {
        this.showHideWeatherContent(true)
      }
    }
  } as AirQualityDetailPopupOptions)
})
less 复制代码
@Builder
export function AirQualityDetailPopupBuilder(options: AirQualityDetailPopupOptions) {
  AirQualityDetailPopup({ options: options })
}

@ComponentV2
export struct AirQualityDetailPopup {
  @Require @Param options: AirQualityDetailPopupOptions
  @Local panelOpacity: number = 0
  @Local contentOpacity: number = 0
  @Local marginTop: number = 0
  @Local alignment: FlexAlign = FlexAlign.Start

  aboutToAppear(): void {
    this.options.ref.exit = this.exit
    const airQualityPanelRectangle = componentUtils.getRectangleById('weather_air_quality_panel')
    this.marginTop = px2vp(airQualityPanelRectangle.windowOffset.y)
    setTimeout(() => {
      this.panelOpacity = 1
      this.marginTop = 0
      this.alignment = FlexAlign.Center
      setTimeout(() => {
        this.contentOpacity = 1
      }, 200)
    }, 16)
  }

  exit = () => {
    const airQualityPanelRectangle = componentUtils.getRectangleById('weather_air_quality_panel')
    this.alignment = FlexAlign.Start
    this.marginTop = px2vp(airQualityPanelRectangle.windowOffset.y)
    this.contentOpacity = 0
    setTimeout(() => {
      this.panelOpacity = 0
      setTimeout(() => {
        DialogHelper.closeDialog('air_quality_detail_popup')
      }, 200)
    }, 200)
  }

  build() {
    Column() {
      Column() {
        this.airQualityPanel()
        Blank().height(12)
        Grid() {
          this.airQualityItem('PM2.5', this.options.evn?.pm25 ?? 0)
          this.airQualityItem('PM10', this.options.evn?.pm10 ?? 0)
          this.airQualityItem('NO', this.options.evn?.no2 ?? 0, '2')
          this.airQualityItem('SO', this.options.evn?.so2 ?? 0, '2')
          this.airQualityItem('O', this.options.evn?.o3 ?? 0, '3')
          this.airQualityItem('CO', this.options.evn?.co ?? 0)
        }
        .width('100%')
        .columnsTemplate('1fr 1fr 1fr')
        .columnsGap(12)
        .rowsGap(12)
        .maxCount(3)
        .layoutDirection(GridDirection.Row)
        .opacity(this.contentOpacity)
        .animation({ curve: Curve.Ease, duration: 200 })
      }
      .width(`calc(100% - ${2 * 16}vp)`)
      .opacity(this.panelOpacity)
      .margin({ top: this.marginTop })
      .animation({ curve: Curve.Ease, duration: 200 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(this.alignment)
    .animation({ curve: Curve.Ease, duration: 200 })
    .onClick(() => {
      this.exit()
    })
  }

  @Builder
  airQualityPanel() {
    Column() {
      Blank().height(12)
      Row() {
        Text(this.title)
          .fontSize(16)
          .fontColor($r('app.color.special_white'))
          .fontWeight(FontWeight.Bold)
        OpacityLayout({
          child: () => {
            this.queryIcon()
          },
          onTap: () => {
            DialogHelper.showBindSheet(wrapBuilder(AirQualityQueryDialogBuilder), {
              dialogId: 'air_quality_query_dialog',
            })
          }
        })
      }

      Blank().height(12)
      AirQualityBar({ barWidth: '100%', barHeight: 4, aqi: this.options.evn?.aqi ?? 0 })
      Blank().height(10)
      Text(this.desc)
        .fontSize(13)
        .fontColor($r('app.color.special_white'))
    }
    .width('100%')
    .height(Constants.WEATHER_AIR_QUALITY_PANEL_HEIGHT)
    .alignItems(HorizontalAlign.Start)
    .padding({ left: 16, right: 16 })
    .backgroundColor(ColorUtils.alpha(this.options.isDark ? $r('app.color.special_white') :
    $r('app.color.special_black'),
      this.options.panelOpacity))
    .borderRadius(Constants.ITEM_PANEL_RADIUS)
  }

  @Builder
  airQualityItem(title: string, value: number, subscript?: string) {
    GridItem() {
      Column() {
        Row() {
          Text(title)
            .fontSize(16)
            .fontColor($r('app.color.special_white'))
            .fontWeight(FontWeight.Bold)
          Text(subscript ?? '')
            .fontSize(10)
            .fontColor($r('app.color.special_white'))
            .fontWeight(FontWeight.Bold)
            .visibility(StrUtil.isEmpty(subscript) ? Visibility.Hidden : Visibility.Visible)
          Blank().width(2)
          Text('ug/m³')
            .fontSize(12)
            .fontColor(ColorUtils.alpha($r('app.color.special_white'), 0.6))
        }

        Blank().height(12)
        Text(value.toFixed(1))
          .fontSize(18)
          .fontColor($r('app.color.special_white'))
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .height(72)
      .padding({ left: 12, right: 12 })
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Start)
      .backgroundColor(ColorUtils.alpha(this.options.isDark ? $r('app.color.special_white') :
      $r('app.color.special_black'),
        this.options.panelOpacity))
      .borderRadius(Constants.ITEM_PANEL_RADIUS)
    }
  }

  @Builder
  queryIcon() {
    Stack() {
      Image($r('app.media.ic_query_icon'))
        .width(14)
        .height(14)
        .draggable(false)
        .colorFilter(ColorUtils.translateColor($r('app.color.special_white')))
    }
    .padding({
      left: 12,
      top: 4,
      right: 12,
      bottom: 4
    })
  }

  get title() {
    return `${this.options.evn?.aqi} - ${this.options.evn?.aqi_level_name}`
  }

  get desc() {
    return `当前AQI为${this.options.evn?.aqi}`
  }
}

空气质量指数信息弹窗

scss 复制代码
OpacityLayout({
  child: () => {
    this.queryIcon()
  },
  onTap: () => {
    DialogHelper.showBindSheet(wrapBuilder(AirQualityQueryDialogBuilder), {
      dialogId: 'air_quality_query_dialog',
    })
  }
})
less 复制代码
@Builder
export function AirQualityQueryDialogBuilder(options: BaseSheetOptions) {
  AirQualityQueryDialog({ options: options })
}

@ComponentV2
export struct AirQualityQueryDialog {
  @Require @Param options: BaseSheetOptions

  build() {
    Column() {
      Stack({ alignContent: Alignment.TopEnd }) {
        Text('中国国家环境保护部\n空气质量指数及相关信息')
          .width('100%')
          .textAlign(TextAlign.Center)
          .fontSize(20)
          .fontColor($r('app.color.black'))
          .fontWeight(FontWeight.Bold)
          .margin({ top: 12 })

        OpacityLayout({
          child: () => {
            this.closeIcon()
          },
          onTap: () => {
            DialogHelper.closeBindSheet('air_quality_query_dialog')
          }
        })
      }
      .width('100%')

      Row() {
        Stack()
          .width(16)
          .height('100%')
          .linearGradient({
            direction: GradientDirection.Bottom,
            colors: [[$r('app.color.color_00e301'), 0.0], [$r('app.color.color_fdfd01'), 0.2],
              [$r('app.color.color_fd7e01'), 0.4], [$r('app.color.color_f70001'), 0.6],
              [$r('app.color.color_98004c'), 0.8],
              [$r('app.color.color_7d0023'), 1.0]]
          })
          .borderRadius(100)
        Blank().width(12)
        Column() {
          this.content('优:0-50', '空气质量令人满意,基本无空气污染,各类人群可正常活动')
          this.content('良:51-100', '空气质量可接受,但某些污染物可能对极少数异常敏感人群健康有较弱影响')
          this.content('轻度污染:101-150', '儿童、老年人及心脏病、呼吸系统疾病患者应减少长时间、高强度的户外锻炼')
          this.content('中度污染:151-200',
            '儿童、老年人及心脏病、呼吸系统疾病患者应减少长时间、高强度的户外锻炼,一般人群适量减少户外运动')
          this.content('重度污染:201-300',
            '儿童、老年人和心脏病、肺病患者应停留在室内,停止户外运动,一般人群减少户外运动', FlexAlign.Center)
          this.content('严重污染:大于300', '儿童、老年人和病人应当留在室内,避免体力消耗,一般人群应避免户外活动',
            FlexAlign.End)
        }
        .layoutWeight(1)
      }
      .width('100%')
      .padding({ left: 20, right: 20 })
      .layoutWeight(1)
      .margin({ top: 24, bottom: px2vp(AppUtil.getNavigationIndicatorHeight()) + 64 })
    }
    .width('100%')
    .backgroundColor($r('app.color.bg_color'))
    .borderRadius({ topLeft: 24, topRight: 24 })
  }

  @Builder
  closeIcon() {
    Stack() {
      Image($r('app.media.ic_close_icon1'))
        .width(22)
        .height(22)
        .colorFilter(ColorUtils.translateColor($r('app.color.black')))
        .draggable(false)
    }
    .padding(16)
  }

  @Builder
  content(title: string, content: string, justifyContent: FlexAlign = FlexAlign.Start) {
    Column() {
      Text(title)
        .fontSize(18)
        .fontColor($r('app.color.black'))
        .fontWeight(FontWeight.Bold)
      Blank().height(2)
      Text(content)
        .fontSize(15)
        .fontColor($r('app.color.text_color_01'))
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .justifyContent(justifyContent)
    .layoutWeight(1)
  }
}
相关推荐
BensionLZ33 分钟前
HO与OH差异之Navigation
harmonyos
CV工程师丁Sir1 小时前
《HarmonyOS Next状态栏动画实现案例与代码解析》
华为·harmonyos·harmonyos next
ChinaDragonDreamer2 小时前
HarmonyOS:通过键值型数据库实现数据持久化
harmonyos·鸿蒙
二川bro2 小时前
HarmonyOS NEXT(十) :系统集成与调试
华为·wpf·harmonyos
李游Leo5 小时前
HarmonyOS 之 @Require 装饰器自学指南
harmonyos
SameX5 小时前
HarmonyOS Next ohpm-repo 数据存储安全与多实例高可用部署
前端·harmonyos
ChinaDragon6 小时前
HarmonyOS:通过(SQLite)关系型数据库实现数据持久化
harmonyos
ChinaDragon6 小时前
HarmonyOS:MVVM模式
harmonyos
今阳6 小时前
鸿蒙开发笔记-14-应用上下文Context
android·华为·harmonyos
二流小码农8 小时前
鸿蒙开发:父组件如何调用子组件中的方法?
android·ios·harmonyos