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

易得天气

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

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

效果图

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

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了

相关推荐
花先锋队长7 小时前
鸿蒙生态日日新,鸿蒙原生版支付宝下载量突破230万
华为·harmonyos
星之卡比*8 小时前
前端知识点---库和包的概念
前端·harmonyos·鸿蒙
别说我什么都不会9 小时前
使用Multipass编译OpenHarmony工程
操作系统·嵌入式·harmonyos
轻口味11 小时前
【每日学点HarmonyOS Next知识】多继承、swiper容器、事件传递、滚动安全区域、提前加载网络图片
华为·harmonyos·harmonyosnext
别说我什么都不会11 小时前
鸿蒙轻内核M核源码分析系列二一 05 文件系统FatFS
操作系统·嵌入式·harmonyos
Georgewu12 小时前
【HarmonyOS Next】鸿蒙应用故障处理思路详解
前端·vue.js·harmonyos
盖世栗子12 小时前
HarmonyOS中Image组件的缩放模式
harmonyos
幽蓝计划16 小时前
HarmonyOS Next开发教程之地图定位
harmonyos
轻口味20 小时前
【每日学点HarmonyOS Next知识】Web交互、列表拖拽、横屏后布局、Event序列问题、Scroll与Web组合
前端·交互·harmonyos·harmonyosnext
轻口味1 天前
【每日学点HarmonyOS Next知识】截图组件截取列表、Toggle组件、Web组件请求头、列表选择弹窗、游戏加速
前端·游戏·harmonyos·harmonyosnext