阅读量: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坐标初始值 }
具体的代表的意思如下:
实现思路
- 获取屏幕的宽度和高度,通过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); }) }
- 使用@state方法修饰当前(x,y)的坐标位置,当二者的值发生变化时,会更新组件的位置,实现悬浮框跟着手指移动的效果。
: 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; // 触摸结束时间
positionX
- 监听组件的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}`); }) }
- 将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)获取屏幕宽高等属性
触摸事件