介绍
本示例使用position绝对定位实现应用内悬浮窗,并且通过animateTo结合curves动画曲线实现悬浮窗拖拽跟手和松手吸附边缘的弹性动画效果。
效果图预览
使用说明
按住悬浮窗可以拖拽,松开后悬浮窗自动靠左或靠右,如果悬浮窗超出内容区上下边界,自动吸附在边界位置。
实现思路
- 悬浮窗组件使用Stack嵌套video布局,使用属性position绝对定位使组件悬浮。
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.integer.float_window_content_border_radius'))
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.integer.float_window_content_border_radius'),
bottomRight: $r('app.integer.float_window_content_border_radius')
})
}
.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);
})
- 在悬浮窗组件的aboutToAppear中获取应用窗口尺寸,使用窗口宽度减去悬浮窗宽度和右边距让悬浮窗初始靠右。
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));
}
- 使用getWindowAvoidArea获取顶部状态栏高度和底部导航栏高度。
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));
}
- 悬浮窗组件添加onTouchEvent回调,在手指按下时保存触摸点与悬浮窗左上角的偏移量offsetX和offsetY,用于移动时悬浮窗位置的计算。
case TouchType.Down: {
this.offsetX = event.touches[0].x;
this.offsetY = event.touches[0].y;
break;
}
- 手指移动时,获取触摸点相对于应用窗口左上角的X和Y坐标,通过计算设置悬浮窗的position坐标实现拖拽,使用默认参数的弹性跟手动画曲线curves.responsiveSpringMotion结合animateTo实现跟手动画效果。
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;
}
- 手指抬起时,通过判断悬浮窗中心在水平方向位于窗口中心的左侧或右侧设置悬浮窗靠左或靠右,如果悬浮窗超出内容区上下边界,则将悬浮窗设置在边界位置,使用curves.springMotion弹性动画曲线实现吸附边界时的弹性动画效果。
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 // 视图层-悬浮窗首页
模块依赖
- 本实例依赖common模块中的日志工具类logger。
- 本示例依赖动态路由模块来实现页面的动态加载。
参考资料
鸿蒙全栈开发全新学习指南
也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】。
本路线共分为四个阶段:
第一阶段:鸿蒙初中级开发必备技能
第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH
第三阶段:应用开发中高级就业技术
第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH
《鸿蒙 (Harmony OS)开发学习手册》(共计892页)
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:gitee.com/MNxiaona/733GH
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH
鸿蒙入门教学视频:
美团APP实战开发教学:gitee.com/MNxiaona/733GH
写在最后
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
- 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:
gitee.com/MNxiaona/733GH