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应用中使用,通过回调处理点击事件即可集成到业务逻辑中。

回到顶部