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

易得天气

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

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

效果图

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

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了

相关推荐
__Benco1 小时前
OpenHarmony - 小型系统内核(LiteOS-A)(一)
人工智能·harmonyos
明月与玄武4 小时前
HarmonyOS 5 开发环境全解析:从搭建到实战
华为·harmonyos·harmonyos 5开发环境
飞露4 小时前
HarmonyOS应用开发的工程目录结构
华为·harmonyos
别说我什么都不会5 小时前
【仓颉三方库】 网络组件——DownLoad4cj
harmonyos
别说我什么都不会9 小时前
【仓颉三方库】 网络组件——upload4cj
harmonyos
simple_lau11 小时前
鸿蒙应用如何配置多环境
harmonyos
simple_lau11 小时前
如何从零到一开始搭建鸿蒙项目
harmonyos
HZW897013 小时前
鸿蒙应用开发—数据持久化之SQLite
android·前端·harmonyos
别说我什么都不会13 小时前
【仓颉三方库】 网络组件——rpc4cj
harmonyos
白羊@14 小时前
鸿蒙案例---生肖抽卡
前端·javascript·华为·harmonyos