各位开发者小伙伴们好呀!我是青蓝逐码的云杰~还记得之前咱们一起拆解过各种手势交互的实现逻辑,也聊过我在项目里踩过的那些手势适配坑吗?最近我又在鸿蒙 UI 开发中碰到了一个有意思的需求 —— 通过运动盘组件实现人物移动控制。本想着去各大技术论坛薅点现成代码 “借鉴” 一下,结果翻遍了,来回折腾了好几版都卡在交互流畅度上,简直让人头秃(扶额)。

痛定思痛之后,我决定从零开始梳理这个运动盘交互方案,考虑到很多小伙伴在开发时可能会像我一样优先搜索现成方案,文末老规矩,会把包含手势库、运动盘组件和完整交互逻辑的项目代码打包开源,大家可以直接在鸿蒙开发环境中导入使用,把更多精力留给项目的核心业务开发~

开发过程

草稿

脑子不够用啦,在开发过程中避不开要用到🖊,事实证明挺好用的哈哈哈

成果展示

再结合一下卡牌人,来控制他的位置,我们开发一个低配版王者哈哈(开玩笑)

让我们进入正题吧!!

开发

  1. 大轮盘和小轮盘
    Column() {
      Stack() {
        //外层大轮盘
        Image($r('app.media.turntableBackGround'))
          .width(120)
          .height(120)
          .zIndex(1)

        //小运动摇杆
        Image($r('app.media.smallTurntable'))
          .width(30)
          .height(30)
      }
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#848484')

  1. 小轮盘加滑动手势

            //小运动摇杆
            Image($r('app.media.smallTurntable'))
              .width(30)
              .height(30)
              .translate({ x: this.offsetX, y: this.offsetY, z: 0 })
              .zIndex(3)
              .gesture(
                PanGesture({ fingers: 1 })
                  .onActionUpdate((event: GestureEvent) => {
                    if (event) {
                      this.offsetX = this.positionX + event.offsetX
                      this.offsetY = this.positionY + event.offsetY
                    }
                  })
                  .onActionEnd((event) => {
                    this.offsetX = 0
                    this.offsetY = 0
                  })
              )
    

  1. 给移动的位置加限制

       checkBoundary = () => {
        //限制范围
        let distanceZ = parseInt(Math.sqrt(Math.pow(this.offsetX, 2) + Math.pow(this.offsetY, 2)).toString())
        if (distanceZ > 45) {
          this.offsetX = (this.offsetX / distanceZ * 45);
          this.offsetY = (this.offsetY / distanceZ * 45);
        }
       }
    

这段代码的功能是把offsetXoffsetY这两个坐标所确定的点,限制在以原点(0,0)为中心、半径为45的圆形区域之内。下面来详细解释:

代码分步解析

  • 计算距离
let distanceZ = parseInt(Math.sqrt(Math.pow(this.offsetX, 2) + Math.pow(this.offsetY, 2)).toString())

这里运用了勾股定理,计算从原点(0,0)到点(this.offsetX, this.offsetY)的直线距离。

  • 距离判断与坐标调整
if (distanceZ > 45) {
  this.offsetX = (this.offsetX / distanceZ * 45);
  this.offsetY = (this.offsetY / distanceZ * 45);
}

当计算出的距离大于45时,就会按比例对offsetXoffsetY进行缩放,使新的坐标点刚好落在半径为45的圆上。

这样的坐标限制常用于游戏或交互界面中,目的是保证操作的范围不会超出合理区间。例如:

  • 在虚拟摇杆的设计中,防止用户滑动范围过大。
  • 在拖拽操作里,避免元素被拖出指定区域。

举个例子:

假设初始坐标是(offsetX, offsetY) = (60, 30),我们来看看具体的处理过程:

  1. 计算距离:
    distanceZ = √(60² + 30²) = √(3600 + 900) = √4500 ≈ 67.08
  2. 进行缩放:
    • 新的offsetX = 60 / 67.08 * 45 ≈ 40.25
    • 新的offsetY = 30 / 67.08 * 45 ≈ 20.12
  3. 验证结果:
    √(40.25² + 20.12²) ≈ 45,坐标被成功限制在了圆的边界上。

  1. 加高亮效果

    这一部分要根据具体项目具体加效果,我只给小伙伴提供一下思路哦

      checkBoundary = () => {
        //1.限制范围
        let distanceZ = parseInt(Math.sqrt(Math.pow(this.offsetX, 2) + Math.pow(this.offsetY, 2)).toString())
        if (distanceZ > 45) {
          this.offsetX = (this.offsetX / distanceZ * 45);
          this.offsetY = (this.offsetY / distanceZ * 45);
        }
        //2.计算角度  设置高亮
        let angle = Math.atan2(this.offsetY, this.offsetX);
        //将弧度转换为角度
        this.angleDegrees = Number((angle * (180 / Math.PI)).toFixed(2))
        if (this.angleDegrees < 0) {
          this.angleDegrees += 360;
        }
        if (distanceZ > 40) {
          this.HighlightingAngleShow = true
        } else {
          this.HighlightingAngleShow = false
        }
        /**
         * 给小伙伴自己加高亮效果哦,在大轮盘和小轮盘之间加就行了,记得加照片组件加一下zindex(2)
         */
        // if (angleDegrees >= 22.5 && angleDegrees < 67.5) {
        //   console.log('右下')
        //   this.angleResourse = $r('app.media.rightDown')
        // } else if (angleDegrees >= 67.5 && angleDegrees < 112.5) {
        //   console.log('正下')
        //   this.angleResourse = $r('app.media.down')
        // } else if (angleDegrees >= 112.5 && angleDegrees < 157.5) {
        //   console.log('左下')
        //   this.angleResourse = $r('app.media.leftDown')
        // } else if (angleDegrees >= 157.5 && angleDegrees < 202.5) {
        //   console.log('左')
        //   this.angleResourse = $r('app.media.left')
        // } else if (angleDegrees >= 202.5 && angleDegrees < 247.5) {
        //   console.log('左上')
        //   this.angleResourse = $r('app.media.leftTop')
        // } else if (angleDegrees >= 247.5 && angleDegrees < 292.5) {
        //   console.log('正上')
        //   this.angleResourse = $r('app.media.top')
        // } else if (angleDegrees >= 292.5 && angleDegrees < 337.5) {
        //   console.log('右上')
        //   this.angleResourse = $r('app.media.rightTop')
        // } else {
        //   console.log('右')
        //   this.angleResourse = $r('app.media.right')
        // }
      }
    

    这段代码主要完成两个任务:一是计算坐标点相对于原点的角度,二是依据距离来控制高亮显示。下面为你详细解释:

    角度计算原理

    1. 使用atan2计算弧度

      let angle = Math.atan2(this.offsetY, this.offsetX);
      
      • atan2(y, x)函数会返回从X轴正方向逆时针旋转到点(x, y)所形成的角度,单位是弧度。
      • 该角度的取值范围是π(也就是-180°180°)。
      • 例如,点(1, 1)的角度是π/4(即45°),点(-1, 1)的角度是3π/4(即135°)。
    2. 弧度转换为角度并取整

      this.angleDegrees = Number((angle * (180 / Math.PI)).toFixed(2))
      
      • 通过angle * (180 / Math.PI)把弧度转换为角度。
      • toFixed(2)将结果保留两位小数,然后用Number()把字符串转换回数字。
    3. 将负角度转换为0-360度范围

      if (this.angleDegrees < 0) {
        this.angleDegrees += 360;
      }
      
      • 把负角度(像-45°)转换为对应的正角度(即315°)。

    高亮显示控制逻辑

    if (distanceZ > 40) {
      this.HighlightingAngleShow = true
    } else {
      this.HighlightingAngleShow = false
    }
    
    • 当坐标点到原点的距离大于40时,就会显示角度高亮效果。
    • 结合上一段代码的限制范围(半径45),这个高亮效果会在距离处于40-45之间时显示。

开源地址

手势项目

青蓝逐码开源项目

结语

``
- atan2(y, x)函数会返回从X轴正方向逆时针旋转到点(x, y)所形成的角度,单位是弧度。
- 该角度的取值范围是π(也就是-180°180°)。
- 例如,点(1, 1)的角度是π/4(即45°),点(-1, 1)的角度是3π/4(即135°)。

  1. 弧度转换为角度并取整

    this.angleDegrees = Number((angle * (180 / Math.PI)).toFixed(2))
    
    • 通过angle * (180 / Math.PI)把弧度转换为角度。
    • toFixed(2)将结果保留两位小数,然后用Number()把字符串转换回数字。
  2. 将负角度转换为0-360度范围

    if (this.angleDegrees < 0) {
      this.angleDegrees += 360;
    }
    
    • 把负角度(像-45°)转换为对应的正角度(即315°)。

高亮显示控制逻辑

if (distanceZ > 40) {
  this.HighlightingAngleShow = true
} else {
  this.HighlightingAngleShow = false
}
  • 当坐标点到原点的距离大于40时,就会显示角度高亮效果。
  • 结合上一段代码的限制范围(半径45),这个高亮效果会在距离处于40-45之间时显示。

[外链图片转存中…(img-ALEe1mJq-1747301743629)]

开源地址

手势项目

青蓝逐码开源项目

结语

技术探索的乐趣就在于不断拆解问题、重构方案,这次从「CV 失败」到「自主实现」的过程,让我更深切体会到:现成代码能节省时间,但理解背后的交互逻辑才能真正解决复杂场景的问题。如果你在开发中遇到类似的手势交互难题,或者想了解鸿蒙系统特有的触摸事件优化技巧,欢迎在评论区留言讨论,我会第一时间和大家讨论,或者也可以关注一下我们的组织——青蓝逐码,我们的官网是https://www.qinglanzhuma.cn/

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐