HarmonyOS 鸿蒙Next中页面悬浮球组件
HarmonyOS 鸿蒙Next中页面悬浮球组件
这是一个悬浮球的组件,支持手势拖动 松手吸附效果
interface ballPositionModel {
x: number;
y: number;
}
@Component
export struct FloatingBallView {
@Prop ballType: number;
@Prop isDraggable: boolean = true; //是否可拖动
@Prop bottomTitle: string
@State ballPosition: ballPositionModel = { x: 0, y: 0 }; // 初始位置
// 新增回调属性
onBallClick?: (ballType: number) => void;
private startLeft: number = 0 //开始移动那一刻悬浮按钮距离窗口左边距离
private startTop: number = 0 //开始移动那一刻悬浮按钮距离窗口顶部距离
private startX: number = 0 //开始移动触摸点x坐标,相对窗口左上角
private startY: number = 0 //开始移动触摸点y坐标,相对窗口左上角
private winWidth: number = 0 //窗口宽度
private winHeight: number = 0 //窗口高度
private radius: number = 25 //悬浮按钮半径,单位vp
@State statusHeight: number = 0 //状态栏高度
@State bottomAvoidAreaHeight: number = 0 //手机底部规避区域高度
aboutToAppear(): void {
this.getWindowInfo()
}
build() {
Stack({ alignContent: Alignment.BottomEnd }) {
// 根据类型渲染不同悬浮球
if (this.ballType === 0) {
Column() {
Image($r('app.media.default_avatar')).width('23').height('23')
Text(this.bottomTitle).fontSize(12).margin({ top: 5 })
}.width(40).height(40).borderRadius(5)
} else if (this.ballType === 1) {
Column() {
Image($r('app.media.default_avatar')).width(40).height(40).borderRadius(20)
Image($r('app.media.default_avatar')).width(40).height(40).borderRadius(20)
}.width(40)
} else if (this.ballType == 3) {
Image($r('app.media.default_avatar')).width(40).height(40).borderRadius(20)
}
}
.position(this.ballPosition)
// .borderRadius(20)
// .clip(true)
.onTouch((event: TouchEvent) => {
this.onPanelTouch(event);
})
.onClick(() => this.handleClick())
}
private onPanelTouch(event: TouchEvent) {
if (this.isDraggable) {
//手指按下记录初始触摸点坐标、悬浮按钮位置
if (event.type === TouchType.Down) {
this.startX = event.touches[0].windowX
this.startY = event.touches[0].windowY
this.startLeft = this.ballPosition.x
this.startTop = this.ballPosition.y
}
//手指拖动
else if (event.type === TouchType.Move) {
let touch = event.touches[0]
//计算悬浮球与左边距离(x坐标), 当前悬浮球距离左边=开始位置(x轴)+(当前触摸点x坐标-开始移动触摸点x坐标)
let curLeft = this.startLeft + (touch.windowX - this.startX)
//限制悬浮球不能移除屏幕左边
curLeft = Math.max(0, curLeft)
//限制悬浮球不能移除屏幕右边
this.ballPosition.x = Math.min(this.winWidth - 2 * this.radius, curLeft)
//计算悬浮球与顶部距离(y坐标), 当前悬浮球距离顶部=开始位置(y轴)+(当前触摸点y坐标-开始移动触摸点y坐标)
let curTop = this.startTop + (touch.windowY - this.startY)
//限制悬浮球不能移除屏幕上边
curTop = Math.max(0, curTop)
//限制悬浮球不能移除屏幕下边
this.ballPosition.y =
Math.min(this.winHeight - 2 * this.radius - this.bottomAvoidAreaHeight - this.statusHeight, curTop)
}
//松开手指
else if (event.type == TouchType.Up) {
if (this.ballType !== 3) {
animateTo({ duration: 500 }, () => {
if (this.ballPosition.x > this.winWidth / 2) {
this.ballPosition.x = this.winWidth - 50
} else {
this.ballPosition.x = 0
}
})
}
}
}
}
private handleClick() {
// 调用回调方法并传递参数
if (this.onBallClick) {
this.onBallClick(this.ballType)
}
// switch (this.ballType) {
// case 0: // 外卖秒送切换
// router.push({ url: 'pages/DeliveryPage' });
// break;
// case 2: // 拨打电话
// import { phone } from '@kit.TelephonyKit';
// phone.call('123456789');
// break;
// case 3: // 跳精灵聊天
// router.push({ url: 'pages/ChatPage' });
// break;
// }
}
//获取窗口尺寸信息
private getWindowInfo() {
window.getLastWindow(getContext(this), (err, windowClass) => {
if (!err.code) {
//状态栏高度
this.statusHeight = px2vp(windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height)
//获取手机底部规避区域高度
this.bottomAvoidAreaHeight =
px2vp(windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)
.bottomRect
.height)
//获取窗口宽高
let windowProperties = windowClass.getWindowProperties()
this.winWidth = px2vp(windowProperties.windowRect.width)
this.winHeight = px2vp(windowProperties.windowRect.height)
//设置初始位置位于屏幕右下角,演示设置可根据实际调整
if (this.ballType == 1 || this.ballType == 0) {
this.ballPosition.x = this.winWidth - 50
this.ballPosition.y = this.winHeight * 0.8
} else if (this.ballType == 2) {
this.ballPosition.x = this.winWidth - 50
this.ballPosition.y = this.winHeight * 0.8
} else if (this.ballType == 3) {
// todo 每次进入页面都会重新加载位置
this.ballPosition.x = this.winWidth - 50
this.ballPosition.y = this.winHeight * 0.8 - 50
}
}
})
}
}
组件使用示例
FloatingBallView({
ballType: UnDraggableFloatingUtil.BallType.STATIC_SERVICE,
isDraggable: false,
// 添加点击回调
onBallClick: (ballType) => {
console.log('点击类型:', ballType)
}
})
更多关于HarmonyOS 鸿蒙Next中页面悬浮球组件的实战教程也可以访问 https://www.itying.com/category-93-b0.html
3 回复
App 进后台,它依然能在界面上显示吗?
更多关于HarmonyOS 鸿蒙Next中页面悬浮球组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
鸿蒙Next的页面悬浮球组件通过FloatingBall实现,支持拖拽、吸附、点击等交互。开发者可自定义悬浮球样式、位置及事件响应,如展开菜单或执行快捷操作。该组件适用于全局辅助功能,需在UIAbility中配置相应权限与生命周期管理。
这是一个基于ArkTS实现的、功能完整的悬浮球组件。核心特性包括:
1. 手势拖动与边界控制
- 通过
onTouch事件监听实现拖拽,在TouchType.Move时实时计算位置 - 使用
Math.max/min确保悬浮球不超出屏幕边界,考虑了状态栏和底部规避区域
2. 松手吸附效果
- 在
TouchType.Up时触发吸附动画 - 使用
animateTo实现500ms的平滑过渡动画 - 根据当前位置决定吸附到左侧或右侧(
winWidth/2为分界线)
3. 多类型支持
- 通过
ballType属性区分不同类型(0、1、3) - 每种类型有对应的UI布局(Column+Image/Text组合)
4. 窗口适配
aboutToAppear中调用getWindowInfo获取窗口信息- 通过
window.getLastWindow获取状态栏高度、规避区域高度和窗口尺寸 - 使用
px2vp进行单位转换确保适配
5. 交互回调
- 提供
onBallClick回调属性,点击时返回ballType - 可拖动状态通过
isDraggable属性控制
使用注意:
- 组件初始位置根据
ballType动态计算 - 类型3的悬浮球没有吸附效果(代码中特殊处理)
- 需要确保资源文件(如
app.media.default_avatar)存在
这个组件实现了悬浮球的核心交互,可以直接在HarmonyOS Next应用中使用,通过回调处理点击事件即可集成到业务逻辑中。

