HarmonyOS 鸿蒙Next迷你栏适配智感握姿

HarmonyOS 鸿蒙Next迷你栏适配智感握姿 获取到手持状态后如何修改迷你栏左右位置,就像QQ音乐迷你栏目左右切换一样

6 回复

开发者您好,您可以通过[@ohos.multimodalAwareness.motion (动作感知能力)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-awareness-motion)获取握持手状态,参见获取握持手状态开发指导,需要添加权限: ohos.permission.DETECT_GESTURE,具体申请方式请参考声明权限

"requestPermissions":[
    {
      "name" : "ohos.permission.DETECT_GESTURE"
    }
  ]

对HdsTabs的barHeight设置为0,通过自定义tabbar和minibar实现窄屏的智感握姿,参考代码如下:

// 从6.0.2(22)版本开始,无需手动导入HdsTabsAttribute。具体请参考HdsTabs的导入模块说明。
import { HdsTabs, HdsTabsController } from '@kit.UIDesignKit';
import { motion } from '@kit.MultimodalAwarenessKit';
import { BusinessError } from '@kit.BasicServicesKit';

// Tab数据模型
interface TabItemData {
  title: string;
  icon: ResourceStr;
  activeIcon: ResourceStr;
}

@Entry
@Component
struct Index {
  // 初始化HdsTabs控制器
  private controller: HdsTabsController = new HdsTabsController();
  @State currentIndex: number = 0;
  @State barReversed: boolean = false; // 是否交换MiniBar和TabBar的位置
  // Tab数据源
  private tabItems: TabItemData[] = [
    { title: '首页', icon: $r('sys.media.ohos_ic_public_clock'), activeIcon: $r('sys.media.ohos_ic_public_clock') }
  ];
  callback: Callback<motion.HoldingHandStatus> = (data: motion.HoldingHandStatus) => {
    // 1左手,2右手
    console.info(`Succeeded in getting data:${data}.`);
    if (data === 1) {
      this.barReversed = true;
    } else if (data === 2) {
      this.barReversed = false;
    }
  };

  onMotion() {
    try {
      motion.on('holdingHandChanged', this.callback);
      console.info('Succeeded in holdingHandChang on.');
    } catch (err) {
      let error = err as BusinessError;
      console.error('Failed on and err code is ' + error.code);
    }
  }

  offMotion() {
    try {
      motion.off('holdingHandChanged');
      console.info('Succeeded in holdingHandChanged off.');
    } catch (err) {
      let error = err as BusinessError;
      console.error('Failed off and err code is ' + error.code);
    }
  }

  // 单个Tab按钮
  @Builder
  TabItemBuilder(item: TabItemData, index: number) {
    Column() {
      Image(this.currentIndex === index ? item.activeIcon : item.icon)
        .width(40)
        .height(40)
        .fillColor(this.currentIndex === index ? '#007DFF' : '#999999');
      Text(item.title)
        .fontSize(10)
        .fontColor(this.currentIndex === index ? '#007DFF' : '#999999')
        .margin({ top: 2 });
    }
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .onClick(() => {
      this.currentIndex = index;
    });
  }

  @Builder
  TabBuilder() {
    Stack() {
      Column()
        .height(60)
        .width(60)
        .borderRadius(30)
        .foregroundBlurStyle(BlurStyle.Thin,
          {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 1.0
          });
      Row() {
        ForEach(this.tabItems, (item: TabItemData, index: number) => {
          this.TabItemBuilder(item, index);
        });
      };
    }
    .alignContent(Alignment.Center)
    .height('60')
    .width('60');
  }

  // MiniBar
  @Builder
  MiniBarBuilder() {
    Stack() {
      Column()
        .height(50)
        .width(200)
        .borderRadius(25)
        .foregroundBlurStyle(BlurStyle.Thin,
          {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 1.0
          });
      Row() {
        Column() {
          Image($r('app.media.startIcon'))
            .width(40)
            .height(40)
            .borderRadius(40);
        }.width(48).height(48).justifyContent(FlexAlign.Center).margin({ left: 4, right: 4 });

        Text('Hello');

        Column() {
          Image($r('sys.media.ohos_ic_public_pause'))
            .width(40)
            .height(40)
            .borderRadius(40);
        }.width(48).height(48).justifyContent(FlexAlign.Center);
      }
      .justifyContent(FlexAlign.Start);
    }
    .alignContent(Alignment.Start)
    .height(50)
    .width(200);
  }

  // 自定义底部栏
  @Builder
  CustomBottomBar() {
    Row() {
      if (this.barReversed) {
        // ====== 交换后:MiniBar 在左,TabBar 在右 ======
        this.MiniBarBuilder();
        this.TabBuilder();
      } else {
        // ====== 默认:TabBar 在左,MiniBar 在右 ======
        this.TabBuilder();

        this.MiniBarBuilder();
      }
    }
    .padding({ left: 10, right: 10 })
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .height(56)
    .margin({ bottom: 20 });
  }

  aboutToAppear(): void {
    this.onMotion();
  }

  aboutToDisappear(): void {
    this.offMotion();
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      HdsTabs({ controller: this.controller }) {
        TabContent() {
          Scroll() {
            Column() {
              Image($r('app.media.startIcon'));
              Image($r('app.media.startIcon'));
              Image($r('app.media.startIcon'));
              Image($r('app.media.startIcon'));
            };
          };
        }
        .tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), 'Green'));

        TabContent() {
          Image($r('app.media.startIcon'));
        }
        .tabBar(new BottomTabBarStyle($r('sys.media.wifi_router_fill'), 'Blue'));

        TabContent() {
          Image($r('app.media.startIcon'));
        }
        .tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), 'Yellow'));

      }
      .barHeight(0)
      // 设置barOverlap为true,vertical为false,barPosition为BarPosition.End
      .barOverlap(true)
      .barPosition(BarPosition.End)
      .vertical(false);

      this.CustomBottomBar();
    };
  }
}

更多关于HarmonyOS 鸿蒙Next迷你栏适配智感握姿的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


属性HdsTabsFloatingStyle不是有一个adaptToHandedness?,

可以参考本联盟文章其他开发大佬的文章。

鸿蒙Next中迷你栏适配智感握姿需通过系统传感器接口获取握持数据(如压力、角度),利用ArkUI的布局监听或手势事件动态调整迷你栏位置/尺寸。在module.json5中声明握姿感知权限,通过@ohos.sensor或系统订阅接口实时响应姿态变化,确保迷你栏不遮挡交互区域。

在HarmonyOS Next中实现迷你栏根据手握姿态左/右适配,核心是通过 sensor 模块获取设备倾斜角度判断左/右手持握,再动态调整迷你栏组件的 marginoffset 实现移位。以下为关键代码示例:

import sensor from '@ohos.sensor';
import display from '@ohos.display';

@Entry
@Component
struct MiniBar {
  @State marginLeft: number = 0; // 默认右对齐,实际可根据布局调整
  private gravityCallback = (data: sensor.GravityData) => {
    // 通过重力传感器X轴数据判断握姿:X > 2 为右倾(左手持握),X < -2 为左倾(右手持握)
    const isLeftHand = data.x > 2;
    const screenWidth = display.getDefaultDisplaySync().width;
    // 假设迷你栏宽度为200vp,计算新的左/右位置
    if (isLeftHand) {
      this.marginLeft = 16; // 左手持握时迷你栏靠左
    } else {
      this.marginLeft = screenWidth - 216; // 右手持握时靠右,200宽度+16间距
    }
  }

  aboutToAppear() {
    sensor.on(sensor.SensorId.GRAVITY, this.gravityCallback, { interval: 100000000 }); // 100ms
  }

  aboutToDisappear() {
    sensor.off(sensor.SensorId.GRAVITY, this.gravityCallback);
  }

  build() {
    Column() {
      // 迷你栏容器
      Row() {
        Text('♫ 歌曲名称')
          .fontSize(14)
        Progress({ value: 30, total: 100 })
          .width(80)
      }
      .width(200)
      .height(48)
      .backgroundColor(Color.White)
      .borderRadius(24)
      .shadow(ShadowStyle.OUTER_DEFAULT_SM)
      .margin({ left: this.marginLeft }) // 动态左边距实现左右切换
      .animation({ duration: 300, curve: Curve.EaseInOut }) // 平滑移动
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.End) // 底部显示
  }
}

说明:

  1. 重力传感器X轴值大于阈值(如2)判定为左手持握(手机右倾),迷你栏移到左侧;反之移到右侧。
  2. 使用 marginoffset 动态改变组件水平位置,配合 animation 实现QQ音乐般的平滑切换效果。
  3. 阈值和位置数值需根据真实设备调试,也可结合历史数据进行防抖处理。
回到顶部