HarmonyOS Next 悬浮窗拖拽和吸附动画

介绍

本示例使用position绝对定位实现应用内悬浮窗,并且通过animateTo结合curves动画曲线实现悬浮窗拖拽跟手和松手吸附边缘的弹性动画效果。

效果图预览

使用说明

按住悬浮窗可以拖拽,松开后悬浮窗自动靠左或靠右,如果悬浮窗超出内容区上下边界,自动吸附在边界位置。

实现思路

  1. 悬浮窗组件使用Stack嵌套video布局,使用属性position绝对定位使组件悬浮。源码参考FloatWindowMainPage.ets
ts 复制代码
Stack({ alignContent: Alignment.Bottom }) {
  Video({
    src: $rawfile('float_window_video.mp4'),
    controller: this.videoController
  })
    .controls(false)
    .autoPlay(true)
    .loop(true)
    .muted(true)
    .width($r('app.string.float_window_full_size'))
    .onClick(() => {
      this.videoController.requestFullscreen(true);
    })
    .borderRadius($r('app.string.ohos_id_corner_radius_default_l'))
  Text($r('app.string.float_window_live_text'))
    .width($r('app.string.float_window_full_size'))
    .fontSize($r('app.string.ohos_id_text_size_body1'))
    .fontColor($r('app.color.ohos_id_color_background'))
    .textAlign(TextAlign.Center)
    .backgroundColor($r('app.color.ohos_id_color_list_alert'))
    .borderRadius({
      bottomLeft: $r('app.string.ohos_id_corner_radius_default_l'),
      bottomRight: $r('app.string.ohos_id_corner_radius_default_l')
    })
}
.clip(true)
.border({
  width: $r('app.integer.float_window_border_width'),
  color: $r('app.color.ohos_id_color_background')
})
.borderRadius($r('app.string.ohos_id_corner_radius_default_l'))
.width(Constants.FLOAT_WINDOW_WIDTH)
.height(Constants.FLOAT_WINDOW_HEIGHT)
.backgroundColor($r('app.color.ohos_id_color_foreground'))
.position({ x: this.positionX, y: this.positionY })
.onTouch((event: TouchEvent) => {
  this.onTouchEvent(event);
})
  1. 在悬浮窗组件的aboutToAppear中获取应用窗口尺寸,使用窗口宽度减去悬浮窗宽度和右边距让悬浮窗初始靠右。源码参考FloatWindowMainPage.ets
ts 复制代码
  try {
    const properties = windowClass.getWindowProperties();
    // 获取应用窗口宽高
    this.windowRectWidth = px2vp(properties.windowRect.width);
    this.windowRectHeight = px2vp(properties.windowRect.height)
    // 窗口宽度减去悬浮窗宽度和右边距让悬浮窗初始靠右
    this.positionX = this.windowRectWidth - Constants.FLOAT_WINDOW_WIDTH - Constants.PAGE_PADDING;
  } catch (exception) {
    logger.error(TAG, 'Failed to obtain the window properties. Cause: ' + JSON.stringify(exception));
  }
  1. 使用getWindowAvoidArea获取顶部状态栏高度和底部导航栏高度。源码参考FloatWindowMainPage.ets
ts 复制代码
  try {
    const avoidArea = windowClass.getWindowAvoidArea(type);
    // 获取顶部状态栏高度
    this.topRectHeight = px2vp(avoidArea.topRect.height);
    // 获取底部导航栏高度
    this.bottomRectHeight = px2vp(avoidArea.bottomRect.height);
  } catch (exception) {
    logger.error(TAG, 'Failed to obtain the area. Cause:' + JSON.stringify(exception));
  }
  1. 悬浮窗组件添加onTouchEvent回调,在手指按下时保存触摸点与悬浮窗左上角的偏移量offsetX和offsetY,用于移动时悬浮窗位置的计算。源码参考FloatWindowMainPage.ets
ts 复制代码
  case TouchType.Down: {
    this.offsetX = event.touches[0].x;
    this.offsetY = event.touches[0].y;
    break;
  }
  1. 手指移动时,获取触摸点相对于应用窗口左上角的X和Y坐标,通过计算设置悬浮窗的position坐标实现拖拽,使用默认参数的弹性跟手动画曲线curves.responsiveSpringMotion结合animateTo实现跟手动画效果。源码参考FloatWindowMainPage.ets
ts 复制代码
  case TouchType.Move: {
    const windowX: number = event.touches[0].windowX;
    const windowY: number = event.touches[0].windowY;
    // TODO:知识点:跟手动画,推荐使用默认参数的弹性跟手动画曲线curves.responsiveSpringMotion。
    animateTo({ curve: curves.responsiveSpringMotion() }, () => {
      this.positionX = windowX - this.offsetX - Constants.PAGE_PADDING;
      this.positionY = windowY - this.offsetY - this.topRectHeight - Constants.PAGE_PADDING; // 减去手指位置到悬浮窗左上角的y轴偏移和设备顶部状态栏高度
    })
    break;
  }
  1. 手指抬起时,通过判断悬浮窗中心在水平方向位于窗口中心的左侧或右侧设置悬浮窗靠左或靠右,如果悬浮窗超出内容区上下边界,则将悬浮窗设置在边界位置,使用curves.springMotion弹性动画曲线实现吸附边界时的弹性动画效果。源码参考FloatWindowMainPage.ets
ts 复制代码
  case TouchType.Up: {
    // TODO:知识点:通过判断悬浮窗在窗口中的位置,设置悬浮窗贴边,使用curves.springMotion()弹性动画曲线,可以实现阻尼动画效果
    animateTo({ curve: curves.springMotion() }, () => {
      // 判断悬浮窗中心在水平方向是否超过窗口宽度的一半,根据结果设置靠左或靠右
      if (this.positionX > (this.windowRectWidth - Constants.FLOAT_WINDOW_WIDTH) / 2) {
        this.positionX = this.windowRectWidth - Constants.FLOAT_WINDOW_WIDTH - Constants.PAGE_PADDING; // 悬浮窗靠右
      } else {
        this.positionX = Constants.PAGE_PADDING; // 悬浮窗靠左
      }
      // 页面高度
      const pageHeight: number = this.windowRectHeight - this.topRectHeight - this.bottomRectHeight;
      // 判断悬浮窗是否超出内容区上下边界,根据结果将悬浮窗设置在边界位置
      if (this.positionY < Constants.PAGE_PADDING) {
        this.positionY = Constants.PAGE_PADDING;
      } else if (this.positionY > pageHeight - Constants.FLOAT_WINDOW_HEIGHT - Constants.PAGE_PADDING) {
        this.positionY = pageHeight - Constants.FLOAT_WINDOW_HEIGHT - Constants.PAGE_PADDING;
      }
    })
    break;
  }

高性能知识点

不涉及

工程结构&模块类型

复制代码
floatwindow                                  // har类型
|---/src/main/ets/common                        
|   |---Constants.ets                        // 常量
|---/src/main/ets/pages                        
|   |---FloatWindowMainPage.ets              // 视图层-悬浮窗首页

模块依赖

  1. 本实例依赖common模块中的日志工具类logger
  2. 本示例依赖动态路由模块来实现页面的动态加载。

参考资料

@ohos.window (窗口)

显式动画 (animateTo)

@ohos.curves (插值计算)
为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

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

开发基础知识:https://qr21.cn/FV7h05

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

基于ArkTS 开发:https://qr21.cn/FV7h05

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

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题

2.性能优化方向

3.架构方向

4.鸿蒙开发系统底层方向

5.鸿蒙音视频开发方向

6.鸿蒙车载开发方向

7.鸿蒙南向开发方向

相关推荐
网络小白不怕黑40 分钟前
华为设备 QoS 流分类与流标记深度解析及实验脚本
网络·华为
网络小白不怕黑1 小时前
华为交换机堆叠与集群技术深度解析附带脚本
网络·华为
abigale032 小时前
webpack+vite前端构建工具 -11实战中的配置技巧
前端·webpack·node.js
专注API从业者2 小时前
构建淘宝评论监控系统:API 接口开发与实时数据采集教程
大数据·前端·数据库·oracle
Joker`s smile2 小时前
Chrome安装老版本、不同版本,自制便携版本用于前端调试
前端·chrome
weixin_416639972 小时前
爬虫工程师Chrome开发者工具简单介绍
前端·chrome·爬虫
我是如子啊2 小时前
【解决“此扩展可能损坏”】Edge浏览器(chrome系列通杀))扩展损坏?一招保留数据快速修复
前端·chrome·edge
灵性花火2 小时前
Qt的前端和后端过于耦合(0/7)
开发语言·前端·qt
孤水寒月6 小时前
基于HTML的悬窗可拖动记事本
前端·css·html
祝余呀6 小时前
html初学者第一天
前端·html