易得天气
1、添加城市预览功能
2、天气卡片浏览功能
效果图
天气预览页面
@Route({ name: RouterConstants.WEATHER_PREVIEW_PAGE })
@ComponentV2
export struct WeatherPreviewPage {
@Local previewCityData: CityData = ZRouter.getInstance().getParamByKey<CityData>(Constants.CITY_DATA)
@Local weatherPreviewVM: WeatherPreviewViewModel = new WeatherPreviewViewModel()
aboutToAppear(): void {
const weatherData = AppRuntimeData.getInstance().currentCityData?.weatherData
this.weatherPreviewVM.generateWeatherBg(undefined, weatherData?.weatherType, weatherData?.sunrise,
weatherData?.sunset)
this.weatherPreviewVM.obtainWeatherData(this.previewCityData.cityid ?? '')
}
@Builder
functionButton(action: string, block: () => void) {
Text(action)
.textAlign(TextAlign.Center)
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.fontColor($r('app.color.special_white'))
.fontSize(14)
.padding({ left: 16, right: 16 })
.backgroundColor(ColorUtils.alpha(this.weatherPreviewVM.isDark ? $r('app.color.special_white') :
$r('app.color.special_black'), this.weatherPreviewVM.panelOpacity))
.borderRadius(100)
.height(32)
.onClick(block)
}
build() {
NavDestination() {
Stack({ alignContent: Alignment.Top }) {
Stack({ alignContent: Alignment.Top }) {
MultipleStatusLayout({
message: this.weatherPreviewVM.errorMessage,
viewState: this.weatherPreviewVM.viewState,
loadingProgressColor: $r('app.color.special_white'),
contentView: () => {
this.weatherContentList()
}
})
Row() {
this.functionButton('取消', () => {
ZRouter.getInstance().pop()
})
this.functionButton('添加', () => {
ZRouter.getInstance().popWithResult(this.previewCityData)
})
}
.width('100%')
.padding({ left: 12, top: px2vp(AppUtil.getStatusBarHeight()) + 12, right: 12 })
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.height('100%')
.opacity(this.weatherPreviewVM.contentOpacity)
.animation({ duration: 200, curve: Curve.Linear })
}
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: this.weatherPreviewVM.weatherBg
})
}
.hideTitleBar(true)
.height('100%')
.width('100%')
.onReady((context) => {
ZRouter.animateMgr()
.registerAnimParam(this, context)
.setEnterAnimate({ duration: 500, curve: Curve.LinearOutSlowIn })
.setExitAnimate({ duration: 500, curve: Curve.LinearOutSlowIn })
.addAnimateOptions(new TranslateAnimationOptions({ y: '100%' }))
})
.onDisAppear(() => {
ZRouter.animateMgr().unregisterAnim(this)
})
.attributeModifier(ZRouter.animateMgr().modifier(this))
.backgroundColor($r('app.color.bg_color'))
}
@Builder
weatherContentList() {
Stack({ alignContent: Alignment.Top }) {
List({ scroller: this.weatherPreviewVM.listScroller }) {
ForEach(this.weatherPreviewVM.weatherItemsFilter, (item: WeatherItemData) => {
if (item.itemType == Constants.ITEM_TYPE_ALARMS) {
WeatherAlarmsPanel({
weatherItemData: item,
isDark: this.weatherPreviewVM.isDark,
panelOpacity: this.weatherPreviewVM.panelOpacity,
showHideWeatherContent: (show) => {
this.weatherPreviewVM.showHideWeatherContent(show)
}
})
} else if (item.itemType == Constants.ITEM_TYPE_AIR_QUALITY) {
WeatherAirQualityPanel({
weatherItemData: item,
isDark: this.weatherPreviewVM.isDark,
panelOpacity: this.weatherPreviewVM.panelOpacity,
showHideWeatherContent: (show) => {
this.weatherPreviewVM.showHideWeatherContent(show)
}
})
} else if (item.itemType == Constants.ITEM_TYPE_HOUR_WEATHER) {
WeatherHourPanel({
weatherItemData: item,
isDark: this.weatherPreviewVM.isDark,
panelOpacity: this.weatherPreviewVM.panelOpacity
})
} else if (item.itemType == Constants.ITEM_TYPE_DAILY_WEATHER) {
WeatherDailyPanel({
weatherItemData: item,
isDark: this.weatherPreviewVM.isDark,
panelOpacity: this.weatherPreviewVM.panelOpacity
})
} else if (item.itemType == Constants.ITEM_TYPE_OBSERVE) {
WeatherObservePanel({
weatherItemData: item,
itemTypeObserves: this.weatherPreviewVM.itemTypeObserves,
isDark: this.weatherPreviewVM.isDark,
isWeatherHeaderDark: this.weatherPreviewVM.isWeatherHeaderDark,
panelOpacity: this.weatherPreviewVM.panelOpacity,
showHideWeatherContent: (show) => {
this.weatherPreviewVM.showHideWeatherContent(show)
}
})
} else if (item.itemType == Constants.ITEM_TYPE_LIFE_INDEX) {
WeatherLifeIndexPanel({
weatherItemData: item,
isDark: this.weatherPreviewVM.isDark,
panelOpacity: this.weatherPreviewVM.panelOpacity
})
}
}, (item: WeatherItemData) => item.itemType.toString())
if (StrUtil.isNotEmpty(this.source)) {
this.footer()
}
}
.width('100%')
.height(`calc(100% - ${px2vp(AppUtil.getStatusBarHeight()) + Constants.WEATHER_HEADER_MIN_HEIGHT}vp)`)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
.divider({ strokeWidth: 12, color: $r('app.color.transparent') })
.margin({ top: px2vp(AppUtil.getStatusBarHeight()) + Constants.WEATHER_HEADER_MIN_HEIGHT })
.contentStartOffset(Constants.WEATHER_HEADER_MAX_HEIGHT - Constants.WEATHER_HEADER_MIN_HEIGHT)
.borderRadius({ topLeft: Constants.ITEM_PANEL_RADIUS, topRight: Constants.ITEM_PANEL_RADIUS })
.clip(true)
.onDidScroll(() => {
const yOffset = this.weatherPreviewVM.listScroller.currentOffset().yOffset
this.weatherPreviewVM.setListOffset(yOffset +
(Constants.WEATHER_HEADER_MAX_HEIGHT - Constants.WEATHER_HEADER_MIN_HEIGHT))
})
WeatherHeaderWidget({
isWeatherHeaderDark: this.weatherPreviewVM.isWeatherHeaderDark,
weatherItemData: this.weatherPreviewVM.weatherHeaderItemData,
weatherHeaderOffset: this.weatherPreviewVM.listOffset
})
}
.width('100%')
.height('100%')
.padding({ left: Constants.ITEM_PANEL_MARGIN, right: Constants.ITEM_PANEL_MARGIN })
}
get source() {
return this.weatherPreviewVM.weatherItemsFilter?.[0].weatherData?.source?.title
}
@Builder
footer() {
ListItem() {
Column() {
Text(`天气信息来自${this.source}`)
.fontSize(12)
.fontColor(ColorUtils.alpha(this.weatherPreviewVM.isDark ? $r('app.color.special_white') :
$r('app.color.special_black'), 0.4))
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.margin({ bottom: px2vp(AppUtil.getNavigationIndicatorHeight()) + 8 })
}
}
}
城市卡片选择页面
@Builder
export function WeatherCitySelectorBuilder(options: WeatherCitySelectorOptions) {
WeatherCitySelector({ options: options })
}
@ComponentV2
export struct WeatherCitySelector {
@Require @Param options: WeatherCitySelectorOptions
@Local isSwiperShow: boolean = false
@Local hideSwiper: boolean = false
@Local blurAnimValue: number = 0
@Local list?: Array<Pair<CityData | undefined, Array<WeatherItemData> | undefined>>
@Local scaleArr: Array<number> = []
@Local currentCityData?: CityData
@Local currentData?: Array<WeatherItemData>
@Local scaleAnimValue: number = 0.6
private swiperController: SwiperController = new SwiperController()
private screenWidth = px2vp(DisplayUtil.getWidth())
private screenHeight = px2vp(DisplayUtil.getHeight())
private index = 0
aboutToAppear(): void {
this.options.ref.exit = this.exit
setTimeout(() => {
this.blurAnimValue = 1
}, 16)
this.generateData()
.then((list) => {
this.list = list
const scaleArr = list.map((e) => {
return e.first?.cityid == AppRuntimeData.getInstance().currentCityData?.cityid ? 1 : 0.9
})
this.scaleArr = scaleArr
this.isSwiperShow = true
const index = scaleArr.findIndex((e) => e == 1)
if (index > 0) {
this.getUIContext().postFrameCallback(new MyFrameCallback({
onIdleCallback: () => {
this.swiperController.changeIndex(index)
}
}))
}
})
}
async generateData(): Promise<Array<Pair<CityData | undefined, Array<WeatherItemData> | undefined>>> {
const list: Array<Pair<CityData | undefined, Array<WeatherItemData> | undefined>> = []
const currentCityIdList = await PreferencesUtil.get(Constants.CURRENT_CITY_ID_LIST, []) as Array<string>
const query = await appDatabase.cityDataDao.query()
currentCityIdList.forEach((cityId) => {
const cityData = query.find(it => it.key == cityId)
const findCityId = cityData?.cityid ?? ''
if (StrUtil.isNotEmpty(findCityId)) {
const pair = {
first: cityData,
second: WeatherDataUtils.generateWeatherItems(AppRuntimeData.getInstance().currentWeatherCardSort,
AppRuntimeData.getInstance().currentWeatherObservesCardSort,
AppRuntimeData.getInstance().getWeatherData(cityId))
} as Pair<CityData | undefined, Array<WeatherItemData> | undefined>
if (cityId == Constants.LOCATION_CITY_ID) {
list.splice(0, 0, pair)
} else {
list.push(pair)
}
}
})
return list
}
exit = () => {
this.isSwiperShow = false
setTimeout(() => {
this.blurAnimValue = 0
setTimeout(() => {
DialogHelper.closeDialog('weather_city_selector')
}, 200)
}, 200)
}
build() {
Stack() {
Stack()
.width('100%')
.height('100%')
.backgroundColor(ColorUtils.alpha($r('app.color.special_black'), 0.15))
.opacity(this.blurAnimValue)
.blur(this.blurAnimValue * 100)
.animation({ curve: Curve.Linear, duration: 200 })
.onClick(() => {
this.exit()
})
Stack({ alignContent: Alignment.Bottom }) {
this.changeWeatherBgButton()
}
.hitTestBehavior(HitTestMode.Transparent)
.width('100%')
.height('100%')
this.content()
if (!ObjectUtil.isNull(this.currentCityData)) {
WeatherCitySnapshot({
cityData: this.currentCityData,
data: this.currentData,
scaleParams: { x: this.scaleAnimValue, y: this.scaleAnimValue }
})
}
}
.width('100%')
.height('100%')
}
@Builder
content() {
Swiper(this.swiperController) {
ForEach(this.list, (item: Pair<CityData | undefined, Array<WeatherItemData> | undefined>, index: number) => {
WeatherCitySelectorItem({
pair: item, onTap: () => {
if (this.index == index) {
this.switchWeatherCity(index)
} else {
this.swiperController.changeIndex(index, true)
}
}
})
.scale({ x: this.scaleArr[index], y: this.scaleArr[index] })
}, (item: Pair<CityData | undefined, Array<WeatherItemData> | undefined>) => item.first?.key)
}
.direction(Direction.Rtl)
.clip(false)
.loop(false)
.prevMargin(this.screenWidth * 0.2)
.nextMargin(this.screenWidth * 0.2)
.indicator(false)
.customContentTransition({
timeout: 200,
transition: (proxy: SwiperContentTransitionProxy) => {
let scale = 1.0 - (Math.abs(proxy.position)) * 0.1
this.scaleArr[(proxy.index)%(this.scaleArr.length)] = scale
}
})
.onChange((index) => {
this.index = index
})
.curve(Curve.Friction)
.height(this.screenHeight * 0.6)
.opacity(this.hideSwiper ? 0 : (this.isSwiperShow ? 1 : 0))
.translate({ x: this.isSwiperShow ? 0 : -this.screenWidth })
.animation({ curve: curves.interpolatingSpring(14, 1, 170, 17), duration: 200 })
.onClick(() => {
this.exit()
})
}
@Builder
changeWeatherBgButton() {
Text('更改天气背景')
.width(172)
.height(42)
.textAlign(TextAlign.Center)
.fontSize(16)
.fontColor($r('app.color.special_white'))
.borderRadius(100)
.borderWidth(0.5)
.borderColor(ColorUtils.alpha($r('app.color.special_white'), 0.5))
.backgroundColor(ColorUtils.alpha($r('app.color.special_black'), 0.2))
.margin({ bottom: px2vp(AppUtil.getNavigationIndicatorHeight()) + 32 })
.opacity(this.blurAnimValue)
.animation({ curve: Curve.Linear, duration: 200 })
.clickEffect({ level: ClickEffectLevel.HEAVY, scale: 0.8 })
.onClick(() => {
})
}
switchWeatherCity(index: number) {
this.currentCityData = this.list?.[index].first
this.currentData = this.list?.[index].second
this.getUIContext().postFrameCallback(new MyFrameCallback({
onIdleCallback: () => {
this.scaleAnimValue = 1
this.hideSwiper = true
setTimeout(() => {
this.exit()
}, 400)
}
}))
}
}