介绍

本文以备忘录应用为示例,介绍如何在实现标题展开时继续下拉的动效。

效果图预览

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_移动开发

使用说明

  1. 进入页面,起初标题并没有展开,与增加/分享的菜单在同一行。
  2. 进行第一次下拉操作,此时标题展开至目标位置,并显示笔记数目信息,与菜单不在同一行。
  3. 继续下拉,达到阈值距离后,标题与菜单都下拉一段距离,并放大;松手后回弹至第一次下拉后的状态。
  4. 向上滑动,标题折叠,回到初始时的状态。

实现思路

  1. 标题是否展开,通过状态变量isExpanded控制,展开后原先标题置空,将展开后标题设置为可见。
Column() {
     Row() {
       Text(!this.isExpanded ? $r('app.string.memo_title') : '') // 展开前的标题
         .fontSize($r('app.string.init_title_font_size'))
       ......
     }
     .width($r('app.string.layout_100_percent'))
     .padding($r('app.string.layout_25'))
     .margin({ top: $r('app.string.layout_10') })
     .alignItems(VerticalAlign.Center)
     .translate(this.getMenuTranslateOptions())
     .animation({ duration: this.duration, curve: Curve.FastOutLinearIn })
   
     if (this.isExpanded) { // 展开后的标题
       Column() {
         Text($r('app.string.memo_title'))
           .fontSize($r('app.string.expanded_title_font_size'))
         Text($r('app.string.memo_counts'))
           .fontSize($r('app.string.memo_counts_font_size'))
           .fontColor(Color.Grey)
       }
       .width($r('app.string.layout_100_percent'))
       .padding({ left: $r('app.string.layout_25') })
       .margin({ top: $r('app.string.layout_10') })
       .alignItems(HorizontalAlign.Start)
       .translate(this.getTitleTranslateOptions())
       .scale(this.getTitleScaleOptions())
       .animation({ duration: this.duration, curve: Curve.FastOutLinearIn })
       .transition({ type: TransitionType.Insert, translate: { y: Constants.TRANSLATE_Y } })
     }
   }
  1. 内容高度变化,通过状态变量heightValue控制,标题展开时设置为较小值,以提供标题展开后的空间。源码参考TitleExpansion.ets。
Column() {
     List({ space: Constants.SEARCH_MEMO_SPACE }) {
       ListItem() {
         Search({ placeholder: $r('app.string.search_placeholder') })
           .width($r('app.string.layout_100_percent'))
           .height($r('app.string.layout_8_percent'))
           .backgroundColor(Color.White)
       }
   
       LazyForEach(this.memoData, (item: MemoInfo) => { 
         ListItem() {
           MemoItem({ memoItem: item })
         }
       }, (item: MemoInfo) => JSON.stringify(item))
     }
     .scrollBar(BarState.Off)
     .margin({ left: $r('app.string.layout_10'), right: $r('app.string.layout_10') })
     .width($r('app.string.layout_90_percent'))
     .onReachStart(() => { // TODO:知识点:通过onReachStart回调,更新列表是否位于顶部的状态更新,以控制仅在顶部时才触发标题下移放大动效
       this.atStart = true;
     })
   }
   .width($r('app.string.layout_100_percent'))
   .height(this.heightValue) // 通过状态变量heightValue控制内容高度变化
   .animation({ duration: this.duration, curve: Curve.FastOutLinearIn })
   .onTouch((event: TouchEvent) => this.handleTouchEvent(event))
  1. 状态变量更新都在onTouch时间的响应函数handleTouchEvent中处理,根据手指滑动类型TouchType分为三种情况处理:
  • TouchType.Down,即手指下按时:记录按下时的坐标downY,并更新状态变量isMoving为开始滑动、duration为初始动画时长
  • TouchType.Move,即手指滑动时:根据滑动方向的不同,更新滑动距离offsetY、内容高度heightValue、标题是否展开等状态变量
  • TouchType.Up、TouchType.Cancel,即滑动结束时:更新状态变量isMoving为停止滑动、duration为回弹动画时长
handleTouchEvent(event: TouchEvent): void {
     switch (event.type) {
       // 手指下按时
       case TouchType.Down:
         this.downY = event.touches[0].y;
         this.lastMoveY = event.touches[0].y;
         this.isMoving = true;
         this.duration = Constants.ANIMATE_DURATION_DRAG; // 重置动画持续时间
         break;
   
       // 手指滑动时
       case TouchType.Move:
         const delta = event.touches[0].y - this.lastMoveY;
         this.offsetY = event.touches[0].y - this.downY;
         if (delta < 0) { // TODO:知识点:手势为上滑时
           this.heightValue = $r('app.string.memo_area_height_before');
           this.isExpanded = false;
           this.atStart = false;
         }
         if (delta > 0 && this.atStart) { // TODO:知识点:手势为下滑,且列表内容位于顶部时
           // 性能知识点:手指滑动的时,直接使用animateTo,会创建大量的animateTo对象,可以通过节流器优化性能
           this.animateToThrottle(() => {
             this.heightValue = $r('app.string.memo_area_height_after');
             this.isExpanded = true;
           }, 1000);
         }
         this.lastMoveY = event.touches[0].y;
         break;
   
       // 滑动结束时
       case TouchType.Up:
       case TouchType.Cancel:
         this.isMoving = false;
         this.duration = Constants.ANIMATE_DURATION_BACK; // 控制回弹动画时间
         break;
     }
   }
  1. 响应状态变量的动画,通过为标题和菜单绑定属性动画animation实现,标题和菜单的tranlate和scale参数在下拉过程中会更新, 并通过isContinuePullDown判断触发与否。源码参考TitleExpansion.ets。
/**
    * 获取标题展开时,图标继续下拉的Translate参数
    */
   getMenuTranslateOptions(): TranslateOptions {
     return this.isContinuePullDown() ? {
       y: Math.min(this.offsetY - Constants.CONTENT_DOWN_Y_THRESHOLD, Constants.MENU_DOWN_Y_MAX_VALUE)
     } : { y: Constants.INIT_COORDINATE };
   }
   
   /**
    * 获取标题展开时,标题继续下拉的Translate参数
    */
   getTitleTranslateOptions(): TranslateOptions {
     return this.isContinuePullDown() ? {
       y: Math.min(this.offsetY - Constants.CONTENT_DOWN_Y_THRESHOLD, Constants.TITLE_DOWN_Y_MAX_VALUE)
     } : { y: Constants.INIT_COORDINATE };
   }
   
   /**
    * 获取标题展开时,标题缩放的Scale参数
    */
   getTitleScaleOptions(): ScaleOptions {
     return this.isContinuePullDown() ? {
       x: Constants.TITLE_SCALE_MAX_VALUE,
       y: Constants.TITLE_SCALE_MAX_VALUE,
       centerX: Constants.INIT_COORDINATE,
       centerY: Constants.INIT_COORDINATE
     } : {
       x: Constants.TITLE_SCALE_DEFAULT_VALUE,
       y: Constants.TITLE_SCALE_DEFAULT_VALUE,
       centerX: Constants.INIT_COORDINATE,
       centerY: Constants.INIT_COORDINATE
     };
   }
   
   /**
    * 判断是否达到触发继续下拉动效的条件
    */
   isContinuePullDown(): boolean {
     return this.isMoving && this.isExpanded && this.offsetY >= Constants.CONTENT_DOWN_Y_THRESHOLD;
   }

高性能知识点

本示例使用了LazyForEach进行数据懒加载以及显式动画animateTo实现文本偏移。

工程结构&模块类型

marquee                                         // har类型
|---model
|   |---Constants.ets                           // 数据模型层-常量
|   |---DataSource.ets                          // 数据模型层-懒加载数据源
|   |---MemoInfo.ets                        // 数据模型层-数据类型
|   |---MockData.ets                            // 数据模型层-模拟数据
|---view
|   |---MemoItem.ets                            // 视图层-笔记视图
|---page
|   |---TitleExpansion.ets                      // 页面层-方案主页面

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_鸿蒙开发_02

开发基础知识:

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_移动开发_03

基于ArkTS 开发:

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_HarmonyOSNEXT 底部菜单_04

鸿蒙开发面试真题(含参考答案):

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_harmonyos_05

鸿蒙开发面试大盘集篇(共计319页):

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

HarmonyOSNEXT 底部菜单 harmonyos 下拉菜单_HarmonyOSNEXT 底部菜单_06