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

易得天气

完成了天气预报主界面到城市列表的一镜到底效果

首先上效果图(天气预报主界面还未完成)

效果图

一镜到底效果主要分成两部分:

1、天气预报主界面缩放到城市列表对应item位置

2、城市列表在List显示区域做一个从上至下/从下至上的item动画

效果一

1、使用clipShape函数对天气预报主界面进行裁剪

2、使用Animator做动画让天气预报主界面的裁剪动起来

clipShape

arduino 复制代码
.clipShape(new PathShape({ commands: this.weatherMainVM.pathCommands }))
kotlin 复制代码
this.weatherMainVM.pathCommands = this.buildPathCommands(0)
this.weatherMainVM.animatorOptions = this.getUIContext().createAnimator({
  duration: 400,
  delay: 0,
  easing: 'ease',
  iterations: 1,
  fill: "forwards",
  direction: 'normal',
  begin: 0,
  end: 1
})
this.weatherMainVM.animatorOptions.onFrame = (animValue: number) => {
  this.weatherMainVM.animValue = animValue
  this.weatherMainVM.pathCommands = this.buildPathCommands(animValue)
  this.weatherMainVM.contentVisibility = animValue >= 1 ? Visibility.Hidden : Visibility.Visible
}
this.weatherMainVM.animatorOptions.onFinish = () => {
  if (this.weatherMainVM.isShowCityManagerPage) {
    if (this.showCityList) {
      this.showCityList()
    }
  } else {
    if (this.hideCityList) {
      this.hideCityList()
    }
  }
}
typescript 复制代码
private buildPathCommands(animValue: number): string {
  const radius = vp2px(16)
  const offsetY = this.weatherMainVM.offsetY
  const itemHeight = vp2px(98)
  const vy =
    (DisplayUtil.getHeight() - offsetY - itemHeight) * (1 - animValue) + offsetY + itemHeight - radius * animValue
  return `M ${radius * animValue} ${(offsetY + radius) *
    animValue} Q ${radius * animValue} ${offsetY *
    animValue}, ${2 * radius * animValue} ${offsetY *
    animValue} H ${DisplayUtil.getWidth() - 2 * radius * animValue} Q ${DisplayUtil.getWidth() -
    radius * animValue} ${offsetY * animValue},
    ${DisplayUtil.getWidth() - radius * animValue} ${(offsetY + radius) *
    animValue} V ${vy} Q ${DisplayUtil.getWidth() - radius * animValue}
    ${vy + radius * animValue}, ${DisplayUtil.getWidth() -
    2 * radius * animValue} ${vy + radius * animValue}
    H ${2 * radius * animValue} Q ${radius * animValue}
    ${vy + radius * animValue}, ${radius * animValue} ${vy} Z`
}

path commands命令主要用到了以下几个命令:

M = moveto 起始

H = horizontal lineto 水平线

V = vertical lineto 垂直线

Q = quadratic Bézier curve 二次贝塞尔曲线

Z = closepath 闭合(从最后一个点连直线到起始点)

使用大写字母表示绝对位置,小写字母表示相对位置(相对于起点的位置,向右向下为正)。

其中Q命令起点、控制点、终点在一个点就是直角

获取城市列表item在屏幕当中的位置信息

主要通过componentUtils.getRectangleById(id)函数获取item的位置信息

城市列表设置id

bash 复制代码
.id('city_list')

城市列表item设置id

javascript 复制代码
.id(`city_item_${this.index}`)

获取城市列表item位置信息,如果该item不在List显示区域内,会将该item移动到List显示区域内

ini 复制代码
checkItemBorder(addedCityData?: Array<CityData>): componentUtils.ComponentInfo | undefined {
  const currentCityData = AppRuntimeData.getInstance().currentCityData
  if (!ObjectUtil.isNull(currentCityData)) {
    const findIndex =
      addedCityData?.findIndex(it => it.cityid == currentCityData!.cityid) ?? -1
    if (findIndex >= 0) {
      const itemRectangle = componentUtils.getRectangleById(`city_item_${findIndex}`)
      const cityListRectangle = componentUtils.getRectangleById('city_list')
      if (itemRectangle.localOffset.y < 0) {
        this.listScroller.scrollToIndex(findIndex + 1)
        itemRectangle.windowOffset.y = cityListRectangle.windowOffset.y
      } else if (itemRectangle.localOffset.y + itemRectangle.size.height > cityListRectangle.size.height) {
        const dy = itemRectangle.size.height - (cityListRectangle.size.height - itemRectangle.localOffset.y)
        this.listScroller.scrollBy(0, px2vp(dy))
        itemRectangle.windowOffset.y = itemRectangle.windowOffset.y - dy
      }
      return itemRectangle
    }
  }
  return undefined
}

还有一点比较重要,需要给List设置cachedCount

官方文档的一句话

懒加载、非懒加载都只布局List显示区域+List显示区域外cachedCount的内容。

如果不设置cachedCount,在List显示区域外的item将拿不到位置信息

kotlin 复制代码
.cachedCount(this.addedCityData?.length)

效果二

首先给城市列表item添加三个参数

less 复制代码
@Param startIndex: number = 0
@Param endIndex: number = 0
@Param itemOpacity: number = 0

startIndex < endIndex表示List item从startIndex开始到endIndex结束做item动画

startIndex > endindex表示List item从endIndex开始到startIndex结束做item动画

itemOpacity表示item的显示透明度

item加上以下函数

kotlin 复制代码
.opacity(this.itemOpacity)
.scale({ x: this.itemOpacity <= 0 ? 0.9 : 1, y: this.itemOpacity <= 0 ? 0.9 : 1 })
.animation({ duration: this.calDuration(), curve: Curve.Linear })
kotlin 复制代码
calDuration(): number {
  if (this.startIndex <= 0 && this.endIndex <= 0) {
    return 0
  }
  if (this.startIndex > this.endIndex) {
    if (this.index < this.endIndex || this.index > this.startIndex) {
      return 0
    }
    const fixedIndex = this.startIndex - this.index
    return fixedIndex * 100 + 50
  } else {
    if (this.index < this.startIndex || this.index > this.endIndex) {
      return 0
    }
    const fixedIndex = this.index - this.startIndex
    return fixedIndex * 100 + 50
  }
}

下面做个演示

加个点击事件

kotlin 复制代码
this.cityManagerVm.itemOpacity = this.cityManagerVm.itemOpacity == 0 ? 1 : 0
php 复制代码
CityManagerItem({
  。。。  
  startIndex: 0,
  endIndex: 5,
  itemOpacity: this.cityManagerVm.itemOpacity,
  。。。
})

startIndex=0,endIndex=5的效果图

startIndex=3,endIndex=5的效果图

startIndex=5,endIndex=3的效果图

所以要确定startIndex和endIndex的值就可以做城市列表item的动画

ini 复制代码
showCityList(addedCityData?: Array<CityData>) {
  const currentCityData = AppRuntimeData.getInstance().currentCityData
  if (!ObjectUtil.isNull(currentCityData)) {
    const findIndex =
      addedCityData?.findIndex(it => it.cityid == currentCityData!.cityid) ?? -1
    if (findIndex >= 0) {
      const findRectangle = componentUtils.getRectangleById(`city_item_${findIndex}`)
      const cityListRectangle = componentUtils.getRectangleById('city_list')
      const indexes =
        addedCityData?.map((_, index) => index).filter((_, index) => {
          const itemRectangle = componentUtils.getRectangleById(`city_item_${index}`)
          return itemRectangle.localOffset.y + itemRectangle.size.height > 0 &&
            itemRectangle.localOffset.y < cityListRectangle.size.height
        })
      if (indexes) {
        if (findRectangle.localOffset.y > cityListRectangle.size.height * 0.5) {
          this.startIndex = indexes[indexes.length - 1]
          this.endIndex = indexes[0]
        } else {
          this.startIndex = indexes[0]
          this.endIndex = indexes[indexes.length - 1]
        }
        this.itemOpacity = 1
      }
    }
  }
}
javascript 复制代码
hideCityList() {
  this.itemOpacity = 0
}

好了,接下来就是做天气预报主界面的UI了

相关推荐
nashane9 小时前
HarmonyOS Wi-Fi连接用户操作监听全解析:从系统弹框到Promise回调
华为·harmonyos·harmonyos 5
Lanren的编程日记11 小时前
Flutter 鸿蒙应用数据版本管理实战:版本记录+版本回退+版本对比,实现全链路数据版本控制
flutter·华为·harmonyos
木斯佳13 小时前
HarmonyOS 本地存储实战:记账本案例改造实现日历联动
华为·harmonyos
李游Leo14 小时前
别让一张 12MB 的照片拖垮页面:ImageSource / PixelMap / ImagePacker 的工程化处理链路
harmonyos
nashane14 小时前
HarmonyOS 6学习:画中画(PiP)状态同步与场景化实战指南
学习·pip·harmonyos·harmonyos 5
@不误正业15 小时前
鸿蒙小艺智能体开放平台实战-接入系统级AI-Agent能力
人工智能·华为·harmonyos
IntMainJhy18 小时前
「Flutter三方库sqflite的鸿蒙化适配与实战指南:从入门到踩坑的本地数据库开发全记录」
数据库·flutter·华为·信息可视化·数据库开发·harmonyos
前端技术20 小时前
HarmonyOS开发:鸿蒙应用开发发展史
华为·harmonyos
Hello__777721 小时前
开源鸿蒙 Flutter 实战|自定义头像组件全流程实现
flutter·华为·harmonyos
IntMainJhy1 天前
【flutter for open harmony】第三方库Flutter成就解锁彩纸动画的鸿蒙化适配与实战指南
harmonyos