易得天气
1、天气卡片排序
2、天气专业数据卡片排序
效果图
卡片排序页面
@Route({ name: RouterConstants.WEATHER_CARD_SORT_PAGE })
@ComponentV2
export struct WeatherCardSortPage {
@Local isBackEnabled: boolean = true
@Local currentWeatherCardSort: Array<number> = []
@Local currentWeatherObserveCardSort: Array<number> = []
@Local contentOpacity: number = 1
@Local gridOpacity: number = 0
@Local marginTop: number = 0
@Local animDuration: number = 0
private tempMarginTop = 0
aboutToAppear(): void {
this.currentWeatherCardSort =
AppRuntimeData.getInstance().currentWeatherCardSort.filter(it => it != Constants.ITEM_TYPE_WEATHER_HEADER)
this.currentWeatherObserveCardSort = AppRuntimeData.getInstance().currentWeatherObservesCardSort
}
build() {
NavDestination() {
Column() {
CommonTitleBar({
statusBarColor: $r('app.color.transparent'),
titleBarColor: $r('app.color.transparent'),
showBottomLine: false,
leftType: TitleType.CUSTOM,
leftCustomView: () => {
this.leftIcon()
},
centerText: '卡片排序',
centerTextColor: $r('app.color.black'),
rightType: TitleType.TEXT,
rightText: '恢复默认',
rightTextColor: ColorUtils.alpha($r('app.color.black'), this.isBackEnabled ? 1 : 0.2),
rightOnClick: this.isBackEnabled ? () => {
const currentWeatherCardSort = [Constants.ITEM_TYPE_WEATHER_HEADER]
currentWeatherCardSort.push(...this.currentWeatherCardSort)
if (this.compareCurrentWeatherCardSort(currentWeatherCardSort) ||
this.compareCurrentWeatherObserverCardSort(this.currentWeatherObserveCardSort)) {
const defaultWeatherCardSort = [...Constants.DEFAULT_WEATHER_CARD_SORT]
const currentWeatherObserveCardSort = [...Constants.DEFAULT_WEATHER_OBSERVES_CARD_SORT]
AppRuntimeData.getInstance().setCurrentWeatherCardSort(defaultWeatherCardSort)
AppRuntimeData.getInstance().setCurrentWeatherObservesCardSort(currentWeatherObserveCardSort)
this.currentWeatherCardSort =
defaultWeatherCardSort.filter(it => it != Constants.ITEM_TYPE_WEATHER_HEADER)
this.currentWeatherObserveCardSort = currentWeatherObserveCardSort
}
ToastUtil.showToast('已恢复默认')
} : undefined
})
Stack() {
this.weatherCardSortList()
this.weatherCardSortGrid()
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
.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
leftIcon() {
Image($r('app.media.ic_close_icon1'))
.width(20)
.height(20)
.colorFilter(ColorUtils.translateColor($r('app.color.black'), this.isBackEnabled ? 1 : 0.2))
.onClick(this.isBackEnabled ? () => {
ZRouter.getInstance().pop()
} : undefined)
}
@Builder
weatherCardSortList() {
List() {
ListItem() {
Text('首页的天气卡片将会按照以下排序进行展示')
.height(30)
.textAlign(TextAlign.Start)
.align(Alignment.BottomStart)
.padding({ left: 16 })
.fontSize(14)
.fontColor($r('app.color.color_999999'))
}
ForEach(this.currentWeatherCardSort, (item: number) => {
WeatherCardSortListItem({
weatherCardItemType: item,
index: this.currentWeatherCardSort.findIndex(it => it == item),
length: this.currentWeatherCardSort.length,
onReorderStart: () => {
this.isBackEnabled = false
return true
},
onChangeItem: (from, to) => {
if (ArrayUtil.isNotEmpty(this.currentWeatherCardSort)) {
const tmp = this.currentWeatherCardSort.splice(from, 1)
this.currentWeatherCardSort.splice(to, 0, tmp[0])
}
},
onReorderDone: () => {
this.isBackEnabled = true
const currentWeatherCardSort = [Constants.ITEM_TYPE_WEATHER_HEADER]
currentWeatherCardSort.push(...this.currentWeatherCardSort)
AppRuntimeData.getInstance().setCurrentWeatherCardSort(currentWeatherCardSort)
},
onWeatherObserveSortTap: () => {
const index = this.currentWeatherCardSort.findIndex(it => it == item)
if (index >= 0) {
this.contentOpacity = 0
this.animDuration = 0
this.marginTop = index * (48 + 12)
this.tempMarginTop = this.marginTop
setTimeout(() => {
this.animDuration = 200
this.marginTop = 0
this.gridOpacity = 1
}, 16)
}
}
})
}, (item: number) => item.toString())
}
.width('100%')
.height('100%')
.divider({ strokeWidth: 12, color: $r('app.color.transparent') })
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
.opacity(this.contentOpacity)
.animation({ curve: Curve.Ease, duration: 200 })
}
@Builder
weatherCardSortGrid() {
List() {
ListItem() {
Text('专业数据卡片将会按照以下排序进行展示')
.height(30)
.textAlign(TextAlign.Start)
.align(Alignment.BottomStart)
.padding({ left: 16 })
.fontSize(14)
.fontColor($r('app.color.color_999999'))
}
ListItem() {
Row() {
Row() {
Text('专业数据')
.fontSize(16)
.fontColor($r('app.color.black'))
.fontWeight(FontWeight.Bold)
Image($r('app.media.ic_sort_icon'))
.width(18)
.height(18)
.colorFilter(ColorUtils.translateColor($r('app.color.color_999999')))
.margin({ left: 8 })
.draggable(false)
}
Image($r('app.media.ic_close_icon1'))
.width(18)
.height(18)
.colorFilter(ColorUtils.translateColor($r('app.color.color_999999')))
.draggable(false)
.onClick(() => {
this.gridOpacity = 0
this.animDuration = 200
this.marginTop = this.tempMarginTop
setTimeout(() => {
this.contentOpacity = 1
}, 200)
})
}
.width('100%')
.height(48)
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor($r('app.color.card_color_06'))
.borderRadius(6)
.padding({ left: 16, right: 16 })
}
.padding({ left: 16, right: 16 })
.margin({ top: this.marginTop })
.animation({ curve: Curve.Ease, duration: this.animDuration })
ListItem() {
Grid() {
ForEach(this.currentWeatherObserveCardSort, (item: number) => {
this.weatherCardSortGridItem(item, '100%')
}, (item: number) => item.toString())
}
.width('100%')
.scrollBar(BarState.Off)
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.maxCount(2)
.padding({ left: 16, right: 16 })
.layoutDirection(GridDirection.Row)
.opacity(this.gridOpacity)
.animation({ curve: Curve.Ease, duration: 200 })
.editMode(true)
.supportAnimation(true)
.onItemDragStart((_, itemIndex: number) => {
this.isBackEnabled = false
return this.pixelMapBuilder(this.currentWeatherObserveCardSort[itemIndex])
})
.onItemDrop((_, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
if (!isSuccess || insertIndex >= this.currentWeatherObserveCardSort.length) {
return
}
this.isBackEnabled = true
this.changeGridIndex(itemIndex, insertIndex)
})
}
}
.width('100%')
.height('100%')
.divider({ strokeWidth: 12, color: $r('app.color.transparent') })
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
.backgroundColor($r('app.color.bg_color'))
.opacity(1 - this.contentOpacity)
.animation({ curve: Curve.Ease, duration: 200 })
.visibility(this.contentOpacity == 1 ? Visibility.Hidden : Visibility.Visible)
}
@Builder
weatherCardSortGridItem(itemType: number, width: number | string) {
GridItem() {
Row() {
Text(this.getTitle(itemType))
.fontSize(16)
.fontColor($r('app.color.black'))
.fontWeight(FontWeight.Bold)
Image($r('app.media.ic_menu_icon'))
.width(24)
.height(24)
.colorFilter(ColorUtils.translateColor($r('app.color.color_999999')))
.draggable(false)
}
.width(width)
.height(48)
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor($r('app.color.card_color_06'))
.borderRadius(6)
.padding({ left: 16, right: 16 })
}
}
@Builder
pixelMapBuilder(itemType: number) {
this.weatherCardSortGridItem(itemType, (px2vp(DisplayUtil.getWidth()) - 2 * 16 - 12) / 2)
}
changeGridIndex(index1: number, index2: number) {
let tmp = this.currentWeatherObserveCardSort.splice(index1, 1)
this.currentWeatherObserveCardSort.splice(index2, 0, tmp[0])
AppRuntimeData.getInstance().setCurrentWeatherObservesCardSort(this.currentWeatherObserveCardSort)
}
getTitle(itemType: number): string {
switch (itemType) {
case Constants.ITEM_TYPE_OBSERVE_UV:
return "紫外线指数"
case Constants.ITEM_TYPE_OBSERVE_SHI_DU:
return "湿度"
case Constants.ITEM_TYPE_OBSERVE_TI_GAN:
return "体感温度"
case Constants.ITEM_TYPE_OBSERVE_WD:
return "风向";
case Constants.ITEM_TYPE_OBSERVE_SUNRISE_SUNSET:
return "日出日落"
case Constants.ITEM_TYPE_OBSERVE_PRESSURE:
return "气压"
case Constants.ITEM_TYPE_OBSERVE_VISIBILITY:
return "可见度"
case Constants.ITEM_TYPE_OBSERVE_FORECAST40:
return "未来40日天气"
}
return ""
}
compareCurrentWeatherCardSort(currentWeatherCardSort: Array<number>): boolean {
if (currentWeatherCardSort.length != Constants.DEFAULT_WEATHER_CARD_SORT.length) {
return false
}
for (let index = 0; index < currentWeatherCardSort.length; index++) {
if (Constants.DEFAULT_WEATHER_CARD_SORT[index] != currentWeatherCardSort[index]) {
return true
}
}
return false
}
compareCurrentWeatherObserverCardSort(currentWeatherObserverCardSort: Array<number>): boolean {
if (currentWeatherObserverCardSort.length !=
Constants.DEFAULT_WEATHER_OBSERVES_CARD_SORT.length) {
return false
}
for (let index = 0; index < currentWeatherObserverCardSort.length; index++) {
if (Constants.DEFAULT_WEATHER_OBSERVES_CARD_SORT[index] != currentWeatherObserverCardSort[index]) {
return true
}
}
return false
}
}
WeatherCardSortListItem
@ComponentV2
export struct WeatherCardSortListItem {
@Param @Require weatherCardItemType: number
@Param index: number = 0
@Param length: number = 0
@Event onReorderStart?: () => boolean = undefined
@Event beforeChange?: (target: number) => boolean = undefined
@Event onChangeItem?: (from: number, to: number) => void = undefined
@Event onReorderDone?: () => void = undefined
@Event onWeatherObserveSortTap?: () => void = undefined
private isLongPress: boolean = false
private isMenuPress: boolean = false
private dragRefOffset: number = 0
@Local zIndexValue: number = 0
@Local offsetY: number = 0
build() {
ListItem() {
Row() {
Row() {
Text(this.title)
.fontSize(16)
.fontColor($r('app.color.black'))
.fontWeight(FontWeight.Bold)
Image($r('app.media.ic_sort_icon'))
.width(18)
.height(18)
.colorFilter(ColorUtils.translateColor($r('app.color.color_999999')))
.margin({ left: 8 })
.draggable(false)
.visibility(this.weatherCardItemType == Constants.ITEM_TYPE_OBSERVE ? Visibility.Visible :
Visibility.Hidden)
}
Image($r('app.media.ic_menu_icon'))
.width(24)
.height(24)
.colorFilter(ColorUtils.translateColor($r('app.color.color_999999')))
.draggable(false)
.gesture(
GestureGroup(GestureMode.Sequence,
PanGesture()
.onActionStart(() => {
this.onMenuPress()
})
.onActionUpdate((event: GestureEvent) => {
this.onItemMove(event.offsetY)
})
.onActionEnd(() => {
this.onItemDrop()
})
).onCancel(() => {
if (!this.isMenuPress) {
return;
}
this.onItemDrop()
})
)
}
.width('100%')
.height(ITEM_HEIGHT)
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor($r('app.color.card_color_06'))
.borderRadius(6)
.padding({ left: 16, right: 16 })
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture()
.onAction(() => {
this.onLongPress()
}),
PanGesture()
.onActionUpdate((event: GestureEvent) => {
this.onItemMove(event.offsetY)
})
.onActionEnd(() => {
this.onItemDrop()
})
).onCancel(() => {
if (!this.isLongPress) {
return
}
this.onItemDrop()
})
)
.onClick(this.weatherCardItemType == Constants.ITEM_TYPE_OBSERVE ? this.onWeatherObserveSortTap : undefined)
}
.padding({ left: 16, right: 16 })
.zIndex(this.zIndexValue)
.translate({ y: this.offsetY })
}
get title() {
switch (this.weatherCardItemType) {
case Constants.ITEM_TYPE_ALARMS:
return "极端天气"
case Constants.ITEM_TYPE_AIR_QUALITY:
return "空气质量"
case Constants.ITEM_TYPE_HOUR_WEATHER:
return "每小时天气预报"
case Constants.ITEM_TYPE_DAILY_WEATHER:
return "15日天气预报"
case Constants.ITEM_TYPE_OBSERVE:
return "专业数据"
case Constants.ITEM_TYPE_LIFE_INDEX:
return "生活指数"
}
return ""
}
onLongPress() {
const enable = this.onReorderStart ? this.onReorderStart() : true
if (enable) {
this.zIndexValue = 1
this.isLongPress = true
this.dragRefOffset = 0
}
}
onMenuPress() {
const enable = this.onReorderStart ? this.onReorderStart() : true
if (enable) {
this.zIndexValue = 1
this.isMenuPress = true
this.dragRefOffset = 0
}
}
onItemMove(offsetY: number) {
if (!this.isLongPress && !this.isMenuPress) {
return
}
this.offsetY = offsetY - this.dragRefOffset
const direction = this.offsetY > 0 ? 1 : -1
if (Math.abs(this.offsetY) > ITEM_HEIGHT / 2) {
if (this.index === 0 && direction === -1) {
return
}
if (this.index === this.length - 1 && direction === 1) {
return
}
// 目标位置索引
const target = this.index + direction
if (this.beforeChange) {
if (this.beforeChange(target)) {
return
}
}
animateTo({ curve: Curve.Friction, duration: 300 }, () => {
this.offsetY -= direction * ITEM_HEIGHT
this.dragRefOffset += direction * ITEM_HEIGHT
if (target !== -1 && target <= this.length) {
console.log('changeItem index = ' + this.index + ' target = ' + target)
if (this.onChangeItem) {
this.onChangeItem(this.index, target)
}
}
})
}
}
onItemDrop() {
if (this.isLongPress || this.isMenuPress) {
this.isLongPress = false
this.isMenuPress = false
this.dragRefOffset = 0;
if (this.onReorderDone) {
this.onReorderDone()
}
animateTo({
curve: curves.interpolatingSpring(14, 1, 170, 17), onFinish: () => {
this.zIndexValue = 0
}
}, () => {
this.offsetY = 0
})
}
}
}
天气卡片排序之后
onReorderDone: () => {
this.isBackEnabled = true
const currentWeatherCardSort = [Constants.ITEM_TYPE_WEATHER_HEADER]
currentWeatherCardSort.push(...this.currentWeatherCardSort)
AppRuntimeData.getInstance().setCurrentWeatherCardSort(currentWeatherCardSort)
},
setCurrentWeatherCardSort(currentWeatherCardSort: Array<number>) {
this._currentWeatherCardSort = currentWeatherCardSort
PreferencesUtil.put(Constants.CURRENT_WEATHER_CARD_SORT, currentWeatherCardSort)
.then(() => {
EmitterUtil.post(EmitterManager.WEATHER_CARD_SORT_CHANGED_EVENT, undefined)
})
}
EmitterUtil.onSubscribe<undefined>(EmitterManager.WEATHER_CARD_SORT_CHANGED_EVENT, () => {
this.weatherMainVM.reorder(AppRuntimeData.getInstance().currentWeatherCardSort)
})
reorder(currentWeatherSort: Array<number>) {
const newWeatherItems: Array<WeatherItemData> = []
currentWeatherSort.forEach(itemType => {
const find = this.weatherItems?.find(it => it.itemType == itemType)
if (!ObjectUtil.isNull(find)) {
newWeatherItems.push(find!)
}
})
this.weatherItems = newWeatherItems
}
天气专业数据卡片排序后
changeGridIndex(index1: number, index2: number) {
let tmp = this.currentWeatherObserveCardSort.splice(index1, 1)
this.currentWeatherObserveCardSort.splice(index2, 0, tmp[0])
AppRuntimeData.getInstance().setCurrentWeatherObservesCardSort(this.currentWeatherObserveCardSort)
}
setCurrentWeatherObservesCardSort(currentWeatherObservesCardSort: Array<number>) {
this._currentWeatherObservesCardSort = currentWeatherObservesCardSort
PreferencesUtil.put(Constants.CURRENT_WEATHER_OBSERVES_CARD_SORT, currentWeatherObservesCardSort)
.then(() => {
EmitterUtil.post(EmitterManager.WEATHER_OBSERVES_CARD_SORT_CHANGED_EVENT, undefined)
})
}
EmitterUtil.onSubscribe<undefined>(EmitterManager.WEATHER_OBSERVES_CARD_SORT_CHANGED_EVENT, () => {
this.weatherMainVM.reorderObserves()
})
reorderObserves() {
this.generateWeatherItems(this.weatherItems?.[0].weatherData)
}
private generateWeatherItems(weatherData?: WeatherData) {
// 生成天气背景
this.weatherBg = WeatherDataUtils.generateWeatherBg(weatherData)
// 根据天气背景计算天气头部是否是dark模式
this.isWeatherHeaderDark = WeatherDataUtils.isWeatherHeaderDark(this.weatherBg)
// 根据天气背景计算天气内容是否是dark模式
this.isDark = WeatherDataUtils.isDark(this.weatherBg)
// 根据天气背景计算天气面板的透明度
this.panelOpacity = WeatherDataUtils.calPanelOpacity(this.weatherBg)
this.itemTypeObserves =
WeatherDataUtils.getItemTypeObserves(AppRuntimeData.getInstance().currentWeatherObservesCardSort,
Constants.ITEM_TYPE_OBSERVE, weatherData)
// 生成天气items数据
this.weatherItems = WeatherDataUtils.generateWeatherItems(AppRuntimeData.getInstance().currentWeatherCardSort,
this.itemTypeObserves, weatherData)
}