HarmonyOS(40) 悬浮框实现

avatar
作者
猴君
阅读量:0

悬浮框

实现效果和样式

如下图:按住电话悬浮框,随着手指的拖动会滚动,同时当松开手指时如果在屏幕左半边,则自动移动到左边;反之会自动移动停靠到右边。
在这里插入图片描述
构建电话悬浮框的代码:就是一个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)的坐标位置,当二者的值发生变化时,会更新组件的位置,实现悬浮框跟着手指移动的效果。
  positionX: number = 50; // 组件位置X   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]';    struct FloatingWindowSample {   private diameter: number = 120; // 触摸点相对偏移量    positionX: number = 50; // 组件位置X    positionY: number = 500; // 组件位置Y    displayHalfWidth: number = 0; // 屏幕一半的宽    displayHalfHeight: number = 0; // 屏幕一半的高    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; // 触摸结束时间    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)获取屏幕宽高等属性
触摸事件

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!