HarmonyOS(40) 悬浮框实现

打印 上一主题 下一主题

主题 509|帖子 509|积分 1527

实现效果和样式

如下图:按住电话悬浮框,随着手指的拖动会滚动,同时当松开手指时如果在屏幕左半边,则自动移动到左边;反之会自动移动停靠到右边。

构建电话悬浮框的代码:就是一个Column>>Image + Text,然后设置圆角和通过shadow方法设置阴影即可。
  1. @Component
  2. export default struct FloatingWindowComponent {
  3.   private res: Resource = $r('app.media.ic_call_green');
  4.   private tips?: Resource = $r('app.string.Tips_call');
  5.   build() {
  6.     Column() {
  7.       Image(this.res)
  8.         .objectFit(ImageFit.Contain)
  9.         .width('40%')
  10.         .height('40%')
  11.       Text(this.tips)
  12.         .fontSize(12)
  13.         .fontColor($r('app.color.background_green'))
  14.         .fontWeight(FontWeight.Regular)
  15.         .fontFamily($r('app.string.Font_family_regular'))
  16.     }
  17.     .width(80)
  18.     .height(80)
  19.     .backgroundColor($r('app.color.white'))
  20.     .borderRadius(16)
  21.     .shadow({ radius: 15, color: $r('app.color.btn_border_color') })
  22.     .justifyContent(FlexAlign.SpaceAround)
  23.   }
  24. }
复制代码
相关概念

实现随着手指的移动而移动,就必要获取手指当前的(x,y)坐标值。在onTouch事件TouchEvent来获取对应的位置,TouchEvent对象提供了(windowX,windowY)、(displayX,displayY)、(screenX,screenY)(已废弃)三对属性。好比我们想获取手指按下时对应的坐标位置,可以用如下代码:
  1. if (event.type === TouchType.Down) {
  2.     this.moveStartX = event.touches[0].windowX; // 按下时获取X坐标初始值
  3.      this.moveStartY = event.touches[0].windowY; // 按下时获取Y坐标初始值
  4. }
复制代码
详细的代表的意思如下:

实现思路


  • 获取屏幕的宽度和高度,通过display获取屏幕的宽和高,然后通过px2vp将px转换成vp
  1.   aboutToAppear() {
  2.     display.getAllDisplays((err, data) => {
  3.       // 拿到屏幕宽高的一半,作为判断基准值
  4.       this.displayHalfWidth = data[0].width / 2;
  5.       this.displayHalfHeight = data[0].height / 2;
  6.       // 将拿到的px转为vp
  7.       this.displayHalfWidth = px2vp(this.displayHalfWidth);
  8.       this.displayHalfHeight = px2vp(this.displayHalfHeight);
  9.     })
  10.   }
复制代码

  • 利用@state方法修饰当前(x,y)的坐标位置,当二者的值发生变化时,会更新组件的位置,实现悬浮框跟着手指移动的效果。
  1. @State positionX: number = 50; // 组件位置X
  2. @State positionY: number = 500; // 组件位置Y
  3. /下面的系列属性,官方给的demo中都加了@State修饰,其实没必要
  4. moveStartX: number = 0; // X方向起始点
  5. moveStartY: number = 0; // Y方向起始点
  6. moveEndX: number = 0; // X方向终点
  7. moveEndY: number = 0; // Y方向终点
  8. moveSumLengthX: number = 0; // X方向移动距离总和
  9. moveSumLengthY: number = 0; // Y方向移动距离总和
  10. moveStartTime: number = 0; // 触摸开始时间
  11. moveEndTime: number = 0; // 触摸结束时间
复制代码

  • 监听组件的onTouch方法,监听TouchType.Down,TouchType.Move, TouchType.Up事件,盘算手指移动的位置,同时更新positionX和positionY,由于两个变量通过@State修饰,会刷新页面,实现悬浮框跟随者手指移动的效果,核心代码如下:
  1. if (event.type === TouchType.Move) {
  2.   //省略部分代码
  3.   // 跟手过程,使用responsiveSpringMotion曲线
  4.   animateTo({ curve: curves.responsiveSpringMotion() }, () => {
  5.     // 减去半径,以使球的中心运动到手指位置
  6.     this.positionX = event.touches[0].windowX - this.diameter / 2;
  7.     this.positionY = event.touches[0].windowY - this.diameter / 2 - 120;
  8.     Logger.info(TAG, `move end, animateTo x:${this.positionX}, y:${this.positionY}`);
  9.   })
  10. }
复制代码

  • 将positionX和positionY设置给组件的position(x,y)方法:

全部源码

  1. import { curves, display } from '@kit.ArkUI';
  2. import { TitleBar } from '../../../../common/TitleBar'
  3. import FloatingWindowComponent from './FloatingWindowComponent';
  4. import Logger from '../../../../util/Logger';
  5. const TAG = '[FloatingWindowPage]';
  6. @Entry
  7. @Component
  8. struct FloatingWindowSample {
  9.   private diameter: number = 120; // 触摸点相对偏移量
  10.   @State positionX: number = 50; // 组件位置X
  11.   @State positionY: number = 500; // 组件位置Y
  12.   @State displayHalfWidth: number = 0; // 屏幕一半的宽
  13.   @State displayHalfHeight: number = 0; // 屏幕一半的高
  14.   @State moveStartX: number = 0; // X方向起始点
  15.   @State moveStartY: number = 0; // Y方向起始点
  16.   @State moveEndX: number = 0; // X方向终点
  17.   @State moveEndY: number = 0; // Y方向终点
  18.   @State moveSumLengthX: number = 0; // X方向移动距离总和
  19.   @State moveSumLengthY: number = 0; // Y方向移动距离总和
  20.   @State moveStartTime: number = 0; // 触摸开始时间
  21.   @State moveEndTime: number = 0; // 触摸结束时间
  22.   aboutToAppear() {
  23.     display.getAllDisplays((err, data) => {
  24.       // 拿到屏幕宽高的一半,作为判断基准值
  25.       this.displayHalfWidth = data[0].width / 2;
  26.       this.displayHalfHeight = data[0].height / 2;
  27.       // 将拿到的px转为vp
  28.       Logger.info(TAG, `aboutToAppear getAllDisplays data 1 width:${this.displayHalfWidth}, height:${this.displayHalfHeight}`);
  29.       this.displayHalfWidth = px2vp(this.displayHalfWidth);
  30.       this.displayHalfHeight = px2vp(this.displayHalfHeight);
  31.       Logger.info(TAG, `aboutToAppear getAllDisplays data 2 width:${this.displayHalfWidth}, height:${this.displayHalfHeight}`);
  32.     })
  33.   }
  34.   build() {
  35.     Row() {
  36.       Column() {
  37.         TitleBar({ title: $r('app.string.Floating_window') })
  38.           .id('target')
  39.         Row() {
  40.           Row() {
  41.             FloatingWindowComponent()
  42.           }
  43.           .id('floatingWindowComponent')
  44.           .width(80)
  45.           .height(80)
  46.           .position({ x: this.positionX, y: this.positionY })
  47.           .onTouch((event: TouchEvent) => {
  48.             if (event.type === TouchType.Down) {
  49.               this.moveStartX = event.touches[0].windowX; // 按下时获取X坐标初始值
  50.               this.moveStartY = event.touches[0].windowY; // 按下时获取Y坐标初始值
  51.               this.moveStartTime = Date.now(); // 按下时开始时间
  52.               this.moveSumLengthX = 0; // 按下时初始化x方向移动距离
  53.               this.moveSumLengthY = 0; // 按下时初始化y方向移动距离
  54.             }
  55.             if (event.type === TouchType.Move) {
  56.               this.moveEndX = event.touches[0].windowX; // X方向移动的当前位置
  57.               this.moveEndY = event.touches[0].windowY; // Y方向移动的当前位置
  58.               this.moveSumLengthX += Math.abs(this.moveEndX - this.moveStartX); // 每一次移动计算相对于上一次X方向位置的距离
  59.               this.moveSumLengthY += Math.abs(this.moveEndY - this.moveStartY); // 每一次移动计算相对于上一次Y方向位置的距离
  60.               this.moveStartX = this.moveEndX;
  61.               this.moveStartY = this.moveEndY;
  62.               Logger.info(TAG, `move ing, moveSumLengthX:${this.moveSumLengthX}, moveSumLengthY:${this.moveSumLengthY}`);
  63.               // 跟手过程,使用responsiveSpringMotion曲线
  64.               animateTo({ curve: curves.responsiveSpringMotion() }, () => {
  65.                 // 减去半径,以使球的中心运动到手指位置
  66.                 this.positionX = event.touches[0].windowX - this.diameter / 2;
  67.                 this.positionY = event.touches[0].windowY - this.diameter / 2 - 120;
  68.                 Logger.info(TAG, `move end, animateTo x:${this.positionX}, y:${this.positionY}`);
  69.               })
  70.             } else if (event.type === TouchType.Up) {//手指抬起时自动靠边处理
  71.               this.moveEndTime = Date.now();
  72.               let moveDiffTime = this.moveEndTime - this.moveStartTime; // 最后一秒移动的距离
  73.               // 距离
  74.               let s = Math.sqrt((this.moveSumLengthX * this.moveSumLengthX) + (this.moveSumLengthY * this.moveSumLengthY));
  75.               // 时间
  76.               let t = moveDiffTime;
  77.               // 速度
  78.               let v = s / t;
  79.               Logger.info(TAG, `moveEnd, moveSumLengthX:${this.moveSumLengthX}, moveSumLengthY:${this.moveSumLengthY}, moveDiffTime:${moveDiffTime}`);
  80.               Logger.info(TAG, `moveEnd, s:${s}, t:${t}, v:${v}`);
  81.               // 离手时,使用springMotion曲线,且将移动时速度赋值给离手时速度
  82.               animateTo({ curve: curves.springMotion(), tempo: v }, () => {
  83.                 if (this.positionX >= this.displayHalfWidth) {
  84.                   // 如果划到右边,则定位至屏幕右边减去自身宽度80,再减去10留出间隙
  85.                   this.positionX = this.displayHalfWidth * 2 - 90;
  86.                 } else {
  87.                   this.positionX = 10;
  88.                 }
  89.                 if (this.positionY >= this.displayHalfHeight * 2 - 300) {
  90.                   this.positionY = this.displayHalfHeight * 2 - 300;
  91.                 } else if (this.positionY <= 0) {
  92.                   this.positionY = 10;
  93.                 }
  94.                 Logger.info(TAG, `touchUp, animateTo x:${this.displayHalfWidth}, y:100`);
  95.               })
  96.             }
  97.           })
  98.         }
  99.         .width('100%')
  100.         .height('92%')
  101.       }
  102.       .width('100%')
  103.       .height('100%')
  104.       .backgroundColor($r('app.color.background_shallow_grey'))
  105.     }
  106.     .width('100%')
  107.     .height('100%')
  108.   }
  109. }
复制代码
参考资料

源码传送门:
下载“语言-语言基础类库”,运行后进入:动画>专场动画>悬浮窗。即可看到运行效果。

HarmonyOS鸿蒙学习条记(5)@State作用说明和简朴案例
HarmonyOS鸿蒙学习条记(17)获取屏幕宽高等属性
触摸事件

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表