HarmonyOS(40) 悬浮框实现
创始人
2024-11-11 23:07:16

悬浮框

  • 实现效果和样式
  • 相关概念
  • 实现思路
  • 全部源码
  • 参考资料

实现效果和样式

如下图:按住电话悬浮框,随着手指的拖动会滚动,同时当松开手指时如果在屏幕左半边,则自动移动到左边;反之会自动移动停靠到右边。
在这里插入图片描述
构建电话悬浮框的代码:就是一个Column>>Image + Text,然后设置圆角和通过shadow方法设置阴影即可。

 @Component export default struct FloatingWindowComponent {   private res: Resource = $r('app.media.ic_call_green');   private tips?: Resource = $r('app.string.Tips_call');    build() {     Column() {       Image(this.res)         .objectFit(ImageFit.Contain)         .width('40%')         .height('40%')        Text(this.tips)         .fontSize(12)         .fontColor($r('app.color.background_green'))         .fontWeight(FontWeight.Regular)         .fontFamily($r('app.string.Font_family_regular'))     }     .width(80)     .height(80)     .backgroundColor($r('app.color.white'))     .borderRadius(16)     .shadow({ radius: 15, color: $r('app.color.btn_border_color') })     .justifyContent(FlexAlign.SpaceAround)   } } 

相关概念

实现随着手指的移动而移动,就需要获取手指当前的(x,y)坐标值。在onTouch事件TouchEvent来获取对应的位置,TouchEvent对象提供了(windowX,windowY)、(displayX,displayY)、(screenX,screenY)(已废弃)三对属性。比如我们想获取手指按下时对应的坐标位置,可以用如下代码:

 if (event.type === TouchType.Down) {     this.moveStartX = event.touches[0].windowX; // 按下时获取X坐标初始值      this.moveStartY = event.touches[0].windowY; // 按下时获取Y坐标初始值  } 

具体的代表的意思如下:

在这里插入图片描述

实现思路

  1. 获取屏幕的宽度和高度,通过display获取屏幕的宽和高,然后通过px2vp将px转换成vp
  aboutToAppear() {     display.getAllDisplays((err, data) => {       // 拿到屏幕宽高的一半,作为判断基准值       this.displayHalfWidth = data[0].width / 2;       this.displayHalfHeight = data[0].height / 2;       // 将拿到的px转为vp       this.displayHalfWidth = px2vp(this.displayHalfWidth);       this.displayHalfHeight = px2vp(this.displayHalfHeight);     })   }  
  1. 使用@state方法修饰当前(x,y)的坐标位置,当二者的值发生变化时,会更新组件的位置,实现悬浮框跟着手指移动的效果。
 @State positionX: number = 50; // 组件位置X  @State positionY: number = 500; // 组件位置Y  /下面的系列属性,官方给的demo中都加了@State修饰,其实没必要  moveStartX: number = 0; // X方向起始点  moveStartY: number = 0; // Y方向起始点  moveEndX: number = 0; // X方向终点  moveEndY: number = 0; // Y方向终点  moveSumLengthX: number = 0; // X方向移动距离总和  moveSumLengthY: number = 0; // Y方向移动距离总和  moveStartTime: number = 0; // 触摸开始时间  moveEndTime: number = 0; // 触摸结束时间 
  1. 监听组件的onTouch方法,监听TouchType.Down,TouchType.Move, TouchType.Up事件,计算手指移动的位置,同时更新positionX和positionY,因为两个变量通过@State修饰,会刷新页面,实现悬浮框跟随者手指移动的效果,核心代码如下:
if (event.type === TouchType.Move) {   //省略部分代码    // 跟手过程,使用responsiveSpringMotion曲线   animateTo({ curve: curves.responsiveSpringMotion() }, () => {     // 减去半径,以使球的中心运动到手指位置     this.positionX = event.touches[0].windowX - this.diameter / 2;     this.positionY = event.touches[0].windowY - this.diameter / 2 - 120;     Logger.info(TAG, `move end, animateTo x:${this.positionX}, y:${this.positionY}`);   }) } 
  1. 将positionX和positionY设置给组件的position(x,y)方法:
    在这里插入图片描述

全部源码

 import { curves, display } from '@kit.ArkUI'; import { TitleBar } from '../../../../common/TitleBar' import FloatingWindowComponent from './FloatingWindowComponent'; import Logger from '../../../../util/Logger';  const TAG = '[FloatingWindowPage]';  @Entry @Component struct FloatingWindowSample {   private diameter: number = 120; // 触摸点相对偏移量   @State positionX: number = 50; // 组件位置X   @State positionY: number = 500; // 组件位置Y   @State displayHalfWidth: number = 0; // 屏幕一半的宽   @State displayHalfHeight: number = 0; // 屏幕一半的高   @State moveStartX: number = 0; // X方向起始点   @State moveStartY: number = 0; // Y方向起始点   @State moveEndX: number = 0; // X方向终点   @State moveEndY: number = 0; // Y方向终点   @State moveSumLengthX: number = 0; // X方向移动距离总和   @State moveSumLengthY: number = 0; // Y方向移动距离总和   @State moveStartTime: number = 0; // 触摸开始时间   @State moveEndTime: number = 0; // 触摸结束时间    aboutToAppear() {     display.getAllDisplays((err, data) => {       // 拿到屏幕宽高的一半,作为判断基准值       this.displayHalfWidth = data[0].width / 2;       this.displayHalfHeight = data[0].height / 2;       // 将拿到的px转为vp       Logger.info(TAG, `aboutToAppear getAllDisplays data 1 width:${this.displayHalfWidth}, height:${this.displayHalfHeight}`);       this.displayHalfWidth = px2vp(this.displayHalfWidth);       this.displayHalfHeight = px2vp(this.displayHalfHeight);       Logger.info(TAG, `aboutToAppear getAllDisplays data 2 width:${this.displayHalfWidth}, height:${this.displayHalfHeight}`);     })   }    build() {     Row() {       Column() {         TitleBar({ title: $r('app.string.Floating_window') })           .id('target')         Row() {           Row() {             FloatingWindowComponent()           }           .id('floatingWindowComponent')           .width(80)           .height(80)           .position({ x: this.positionX, y: this.positionY })           .onTouch((event: TouchEvent) => {             if (event.type === TouchType.Down) {               this.moveStartX = event.touches[0].windowX; // 按下时获取X坐标初始值               this.moveStartY = event.touches[0].windowY; // 按下时获取Y坐标初始值               this.moveStartTime = Date.now(); // 按下时开始时间               this.moveSumLengthX = 0; // 按下时初始化x方向移动距离               this.moveSumLengthY = 0; // 按下时初始化y方向移动距离             }             if (event.type === TouchType.Move) {               this.moveEndX = event.touches[0].windowX; // X方向移动的当前位置               this.moveEndY = event.touches[0].windowY; // Y方向移动的当前位置               this.moveSumLengthX += Math.abs(this.moveEndX - this.moveStartX); // 每一次移动计算相对于上一次X方向位置的距离               this.moveSumLengthY += Math.abs(this.moveEndY - this.moveStartY); // 每一次移动计算相对于上一次Y方向位置的距离               this.moveStartX = this.moveEndX;               this.moveStartY = this.moveEndY;               Logger.info(TAG, `move ing, moveSumLengthX:${this.moveSumLengthX}, moveSumLengthY:${this.moveSumLengthY}`);                // 跟手过程,使用responsiveSpringMotion曲线               animateTo({ curve: curves.responsiveSpringMotion() }, () => {                 // 减去半径,以使球的中心运动到手指位置                 this.positionX = event.touches[0].windowX - this.diameter / 2;                 this.positionY = event.touches[0].windowY - this.diameter / 2 - 120;                 Logger.info(TAG, `move end, animateTo x:${this.positionX}, y:${this.positionY}`);               })             } else if (event.type === TouchType.Up) {//手指抬起时自动靠边处理               this.moveEndTime = Date.now();               let moveDiffTime = this.moveEndTime - this.moveStartTime; // 最后一秒移动的距离               // 距离               let s = Math.sqrt((this.moveSumLengthX * this.moveSumLengthX) + (this.moveSumLengthY * this.moveSumLengthY));               // 时间               let t = moveDiffTime;               // 速度               let v = s / t;               Logger.info(TAG, `moveEnd, moveSumLengthX:${this.moveSumLengthX}, moveSumLengthY:${this.moveSumLengthY}, moveDiffTime:${moveDiffTime}`);               Logger.info(TAG, `moveEnd, s:${s}, t:${t}, v:${v}`);                // 离手时,使用springMotion曲线,且将移动时速度赋值给离手时速度               animateTo({ curve: curves.springMotion(), tempo: v }, () => {                 if (this.positionX >= this.displayHalfWidth) {                   // 如果划到右边,则定位至屏幕右边减去自身宽度80,再减去10留出间隙                   this.positionX = this.displayHalfWidth * 2 - 90;                 } else {                   this.positionX = 10;                 }                 if (this.positionY >= this.displayHalfHeight * 2 - 300) {                   this.positionY = this.displayHalfHeight * 2 - 300;                 } else if (this.positionY <= 0) {                   this.positionY = 10;                 }                 Logger.info(TAG, `touchUp, animateTo x:${this.displayHalfWidth}, y:100`);               })             }           })         }         .width('100%')         .height('92%')       }       .width('100%')       .height('100%')       .backgroundColor($r('app.color.background_shallow_grey'))     }     .width('100%')     .height('100%')   } }   

参考资料

源码传送门:
下载“语言-语言基础类库”,运行后进入:动画>专场动画>悬浮窗。即可看到运行效果。
在这里插入图片描述
HarmonyOS鸿蒙学习笔记(5)@State作用说明和简单案例
HarmonyOS鸿蒙学习笔记(17)获取屏幕宽高等属性
触摸事件

相关内容

热门资讯

裸辞做“一人公司”,我后悔了 去年这个时候,一位以色列程序员正在东南亚旅行。他顺手把一个在脑子里转了很久的想法做成了产品,一个让任...
南京建成国内首个Pre-6G试... 4月21日,2026全球6G技术与产业生态大会在南京开幕。全息互动技术展台前,一名远在北京的工作人员...
超梵求职受邀参加“2025抖音... 超梵求职受邀参加“2025抖音巨量引擎成人教育行业生态大会”,探讨分享优质内容传播,服务万千学员。 ...
摩托罗拉Razr 2026(R... IT之家 4 月 22 日消息,摩托罗拉宣布新一代 Razr 折叠手机将于 4 月 29 日在美国发...
库克卸任,特纳斯领航:苹果新纪... 苹果首席执行官蒂姆·库克将卸任,硬件工程主管约翰·特纳斯将接任,苹果公司今天宣布此事。 库克将在夏季...