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

易得天气

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

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

效果图

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

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了

相关推荐
见山是山-见水是水8 小时前
鸿蒙Divider 分割线组件完全指南:内容分组、视觉分区与自定义样式
华为·harmonyos
贾伟康9 小时前
【知律|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
harmonyos·arkts·隐私合规·appgallery·应用权限
Kevin Coding20 小时前
JsonConvert:适用于 Android、鸿蒙与 Flutter 的 JSON 转 Model 插件
android·flutter·harmonyos
m0_749690231 天前
【寻迹校园 HarmonyOS NEXT 实战 28】不交换手机号也能交接:固定校内交接点的隐私设计
华为·harmonyos·arkts·产品设计·隐私设计·安全交接
Magic-ZYJ1 天前
HarmonyOS Stage 模型实战:UIAbility 生命周期如何驱动页面安全状态
安全·华为·harmonyos·鸿蒙·移动端开发·独立开发者·心晴手记
贾伟康1 天前
【中国方言题库|09】HarmonyOS ArkTS 方言搜索实战:实现词语检索和无结果反馈
harmonyos·arkts·状态管理·arkui·本地搜索
Dovis(誓平步青云)1 天前
从手机单栏到平板分栏:任务清单的筛选、选中态与不可变更新
华为·harmonyos
梦想不只是梦与想1 天前
鸿蒙 AppGallery Connect:查看应用信息(三)
harmonyos·appgallery·client id·app id·developer id
贾伟康1 天前
【中国方言题库|03】HarmonyOS ArkTS 四川话分库实战:复用题库组件并保持地区参数清晰
harmonyos·arkts·arkui·路由传参·组件复用
贾伟康1 天前
【中国方言题库|10】HarmonyOS ArkTS 语音播放实战:管理读音播放与页面生命周期
生命周期·harmonyos·arkts·语音合成·texttospeech