悬浮框
实现效果和样式
如下图:按住电话悬浮框,随着手指的拖动会滚动,同时当松开手指时如果在屏幕左半边,则自动移动到左边;反之会自动移动停靠到右边。
构建电话悬浮框的代码:就是一个Column>>Image + Text,然后设置圆角和通过shadow方法设置阴影即可。
java
@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)(已废弃)三对属性。比如我们想获取手指按下时对应的坐标位置,可以用如下代码:
dart
if (event.type === TouchType.Down) {
this.moveStartX = event.touches[0].windowX; // 按下时获取X坐标初始值
this.moveStartY = event.touches[0].windowY; // 按下时获取Y坐标初始值
}
具体的代表的意思如下:
实现思路
- 获取屏幕的宽度和高度,通过display获取屏幕的宽和高,然后通过px2vp将px转换成vp
java
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)的坐标位置,当二者的值发生变化时,会更新组件的位置,实现悬浮框跟着手指移动的效果。
dart
@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; // 触摸结束时间
- 监听组件的onTouch方法,监听TouchType.Down,TouchType.Move, TouchType.Up事件,计算手指移动的位置,同时更新positionX和positionY,因为两个变量通过@State修饰,会刷新页面,实现悬浮框跟随者手指移动的效果,核心代码如下:
java
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)方法:
全部源码
dart
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)获取屏幕宽高等属性
触摸事件