HarmonyOS鸿蒙Next中用ArkTS开发时,如何监听屏幕旋转并动态调整布局?

HarmonyOS鸿蒙Next中用ArkTS开发时,如何监听屏幕旋转并动态调整布局? 我们应用中希望横屏时显示双人画中画,竖屏时切回单人全屏。 有推荐的响应式方案吗?

7 回复

推荐使用 WindowStage + 响应式布局

// 监听窗口尺寸变化
windowClass.on('sizeChange', (size) => {
  if (size.width > size.height) {
    // 横屏:启用双窗格布局
    this.layoutMode = 'landscape';
  } else {
    // 竖屏:单窗格
    this.layoutMode = 'portrait';
  }
});

配合 @Builderif/else 动态构建 UI,或使用 Flex + displayPriority 实现自适应。避免硬编码宽高,优先使用百分比或 Resource 中的断点配置。

更多关于HarmonyOS鸿蒙Next中用ArkTS开发时,如何监听屏幕旋转并动态调整布局?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开发者您好,可以采用如下方案:

【解决方案】

  • 方案一:监听屏幕旋转的能力可以通过display.on(‘change’)监听屏幕状态改变实现,并通过display.getDefaultDisplaySync()获取屏幕实例对象,其中rotation属性用于显示设备的屏幕顺时针旋转角度。实现示例如下:

    import window from '[@ohos](/user/ohos).window';
    import display from '[@ohos](/user/ohos).display';
    import { common } from '@kit.AbilityKit';
    
    const ORIENTATION: Array<string> = ['垂直', '水平', '反向垂直', '反向水平'];
    
    @Entry
    @Component
    struct Scene1 {
      @State rotation: number = 0;
      @State message: string = ORIENTATION[this.rotation];
      // 是否横屏状态
      @State @Watch('setWindowLayOut') isLandscape: boolean = false;
    
      aboutToAppear() {
        // 监听屏幕状态改变
        display.on('change', async () => {
          // 获取当前旋转角度
          this.rotation = display.getDefaultDisplaySync().rotation;
          this.message = ORIENTATION[this.rotation];
        });
      }
    
      setWindowLayOut() {
        // 调用该接口手动改变设备横竖屏状态(设置全屏模式,先强制横屏,再加上传感器模式)
        let context = this.getUIContext()?.getHostContext() as common.UIAbilityContext;
        window.getLastWindow(context).then((windowClass) => {
          if (this.isLandscape) {
            // 设置屏幕横屏
            windowClass.setPreferredOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE);
          } else {
            // 设置屏幕竖屏
            windowClass.setPreferredOrientation(window.Orientation.AUTO_ROTATION_PORTRAIT);
          }
        });
      }
    
      build() {
        Row() {
          Column() {
            Text(`${this.rotation}`)
              .fontSize(25)
            Text(`${this.message}`)
              .fontSize(25)
            Button(this.isLandscape ? '竖屏' : '横屏')
              .width(140)
              .onClick(() => {
                // 设置横屏
                this.isLandscape = !this.isLandscape;
              });
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
  • 方案二:可在EntryAbility的onWindowStageCreate中注册windowsSizeChange事件,并通过AppStorage存储,在页面中通过StorageProp监听数据的变化实现屏幕旋转的监听,可参考以下示例:

    • 在EntryAbility中注册监听窗口的windowsSizeChange事件,并使用StorageProp存储屏幕的显示方向:
      import { UIAbility } from '@kit.AbilityKit';
      import { hilog } from '@kit.PerformanceAnalysisKit';
      import { display, window } from '@kit.ArkUI';
      
      export default class EntryAbility extends UIAbility {
        onCreate(): void {
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
        }
      
        onDestroy(): void {
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
        }
      
        onWindowStageCreate(windowStage: window.WindowStage): void {
      
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
      
          let mainWindow: window.Window;
          try {
            mainWindow = windowStage.getMainWindowSync();
            let displayClass: display.Display = display.getDefaultDisplaySync();
            AppStorage.setOrCreate('orientation', displayClass.orientation);
            // 监听窗口的windowsSizeChange事件,旋转屏时会触发该事件
            mainWindow.on('windowSizeChange', () => {
              let displayClass: display.Display | null = null;
              try {
                displayClass = display.getDefaultDisplaySync();
                // 获取屏幕的显示方向
                AppStorage.set('orientation', displayClass.orientation);
              } catch {
                return;
              }
            });
          } catch {
            hilog.info(0x0000, 'testTag', '%{public}s', 'error');
            return;
          }
      //此处需要替换为需要展示的页面
          windowStage.loadContent('pages/Scene2', (err) => {
            if (err.code) {
              return;
            }
            hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
          });
        }
      
        onWindowStageDestroy(): void {
          // Main window is destroyed,release UI related resources
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
        }
      
        onForeground(): void {
          // Ability has brought to foreground
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
        }
      
        onBackground(): void {
          // Ability has back to background
          hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
        }
      }
      
    • 页面中通过StorageProp监听数据的变化实现屏幕旋转的监听:
      import { display } from '@kit.ArkUI';
      
      @Entry
      @Component
      struct Scene2 {
        // 获取通过监听窗口的windowsSizeChange事件得到的屏幕显示方向
        @StorageProp('orientation') myOrientation: display.Orientation = 0;
      
        build() {
          Stack() {
            // 当屏幕显示方向变化时,切换组件的视图效果
            if (this.myOrientation === 0 ||
              this.myOrientation === 2) {
              Image($r('app.media.startIcon'))
                .size({ width: 100, height: 100 })
                .id('image1')
            } else {
              Image($r('app.media.background'))
                .position({ x: 0, y: 0 })
                .size({ width: 200, height: 200 })
                .id('image2')
            }
          }
          .backgroundColor(Color.White)
          .size({ width: '100%', height: '100%' })
        }
      }
      
    • 需要在项目的module.json5文件中的abilities列表里添加"orientation",指定为"auto_rotation":
      "orientation": "auto_rotation",
      
  • 方案三:通过设备中的重力传感器,可以检测该设备在三个坐标轴(即X轴、Y轴及Z轴)上的线性加速度。基于这些数据,能够计算出屏幕当前的旋转状态。有关具体实现方式,可参考以下样例:

    import sensor from '[@ohos](/user/ohos).sensor';
    import base from '[@ohos](/user/ohos).base';
    
    @Entry
    @Component
    struct Scene3 {
      @State rotation: string = 'INVALID';
    
      onDegree(callback: base.Callback<string>): void {
        sensor.on(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => {
          let degree: number = -1;
          degree = this.CalDegree(data.x, data.y, data.z);
          if (degree >= 0 && (degree <= 30 || degree >= 330)) {
            this.rotation = 'ROTATION_0';
          } else if (degree >= 60 && degree <= 120) {
            this.rotation = 'ROTATION_90';
          } else if (degree >= 150 && degree <= 210) {
            this.rotation = 'ROTATION_180';
          } else if (degree >= 240 && degree <= 300) {
            this.rotation = 'ROTATION_270';
          }
          callback(this.rotation);
        });
      }
    
      CalDegree(x: number, y: number, z: number): number {
        let degree: number = -1;
        // 3为有效_增量_角度_阈值_系数
        if ((x * x + y * y) * 3 < z * z) {
          return degree;
        }
        degree = 90 - (Number)(Math.round(Math.atan2(y, -x) / Math.PI * 180));
        return degree >= 0 ? degree % 360 : degree % 360 + 360;
      }
    
      aboutToAppear() {
        let callback = async (rotation: string) => {
          console.info(`rotation = ${rotation}`);
        };
        try {
          this.onDegree(callback);
        } catch (exception) {
        }
      }
    
      build() {
        Row() {
          Column() {
            Text(`${this.rotation}`)
              .fontSize(25)
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
  • 方案四:使用媒体查询接口监听屏幕旋转,设置媒体查询的查询条件为屏幕的方向,可选值为orientation:portrait(设备竖屏)和orientation: landscape(设备横屏)。有关具体实现方式,请参考:[媒体查询 (@ohos.mediaquery)场景示例](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-media-query#场景示例)。

【总结】

四种监听屏幕旋转事件方案的对比:

方案 优缺点 适用场景
方案一:display.on(‘change’) 使用便捷,但受module.json5配置信息的影响。 1. “orientation"指定为"auto_rotation”。 2. 适用于单个页面的旋转事件监听。
方案二:windowsSizeChange 生命周期内将持续保持监听状态,旋转数据需进行传递,且受module.json5配置信息的影响 1. “orientation"指定为"auto_rotation” 。 2. 适用于全局多个页面频繁旋转的监听机制。
方案三:sensor.SensorId.GRAVITY 不受module.json5配置信息的影响,传感器数据需要进行转换,目前不支持模拟器使用。 1. “orientation"指定为非"auto_rotation”。 2. 当前页面无需根据传感器的变动进行旋转,但仍需响应屏幕旋转而执行相应操作的业务场景。
方案四:this.getUIContext().getMediaQuery().matchMediaSync() 该方案的使用较为便捷,然而其可用性受到module.json5配置文件信息的制约,不可在UIAbility的生命周期管理过程中直接调用,而需在完成组件实例的创建之后方可启用。此外,该方案当前无法识别屏幕旋转的横竖屏模式是正向还是反向。 1. “orientation"指定为"auto_rotation”。 2. 只需监听屏幕方向的横竖变化,无需关注横竖方向的具体正反向情况的业务场景。

除可在module.json5文件中全局配置当前UIAbility组件启动时的方向之外,亦可通过调用window.setPreferredOrientation方法动态设定特定页面的窗口方向,请根据具体的业务场景选择合适的方案来监听屏幕旋转事件。

在应用程序开发中,应当如何设置以检测并响应设备屏幕旋转的事件?

方案一:监听屏幕旋转的能力可以通过display.on(‘change’)监听屏幕状态改变实现,并通过display.getDefaultDisplaySync()获取屏幕实例对象,其中rotation属性用于显示设备的屏幕顺时针旋转角度。

import window from '@ohos.window';
import display from '@ohos.display';
import { common } from '@kit.AbilityKit';

const ORIENTATION: Array<string> = ['垂直', '水平', '反向垂直', '反向水平'];

@Entry
@Component
struct Scene1 {
  @State rotation: number = 0;
  @State message: string = ORIENTATION[this.rotation];
  // 是否横屏状态
  @State @Watch('setWindowLayOut') isLandscape: boolean = false;

  aboutToAppear() {
    // 监听屏幕状态改变
    display.on('change', async () => {
      // 获取当前旋转角度
      this.rotation = display.getDefaultDisplaySync().rotation;
      this.message = ORIENTATION[this.rotation];
    });
  }

  setWindowLayOut() {
    // 调用该接口手动改变设备横竖屏状态(设置全屏模式,先强制横屏,再加上传感器模式)
    let context = this.getUIContext()?.getHostContext() as common.UIAbilityContext;
    window.getLastWindow(context).then((windowClass) => {
      if (this.isLandscape) {
        // 设置屏幕横屏
        windowClass.setPreferredOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE);
      } else {
        // 设置屏幕竖屏
        windowClass.setPreferredOrientation(window.Orientation.AUTO_ROTATION_PORTRAIT);
      }
    });
  }

  build() {
    Row() {
      Column() {
        Text(`${this.rotation}`)
          .fontSize(25)
        Text(`${this.message}`)
          .fontSize(25)
        Button(this.isLandscape ? '竖屏' : '横屏')
          .width(140)
          .onClick(() => {
            // 设置横屏
            this.isLandscape = !this.isLandscape;
          });
      }
      .width('100%')
    }
    .height('100%')
  }
}

我也想知道

我也想知道

在ArkTS中,使用window.getLastWindow()获取窗口对象,通过on('windowSizeChange')监听窗口尺寸变化事件,这包含了屏幕旋转。在回调函数中,可以获取新的窗口宽度和高度,据此判断横竖屏状态,并动态更新@State修饰的布局变量,UI会自动刷新。

在HarmonyOS Next中,可以使用display模块的on('orientationChange')接口监听屏幕旋转事件,并结合响应式布局方案动态调整UI。以下是实现方案:

  1. 监听屏幕方向变化

    import display from '[@ohos](/user/ohos).display';
    
    // 监听屏幕旋转
    display.on('orientationChange', (curOrientation) => {
      if (curOrientation === display.Orientation.VERTICAL) {
        // 竖屏:单人全屏布局
        this.mediaLayout = LayoutType.SINGLE_FULL;
      } else if (curOrientation === display.Orientation.HORIZONTAL) {
        // 横屏:双人画中画布局
        this.mediaLayout = LayoutType.DUAL_PIP;
      }
      // 触发UI更新
    });
    
  2. 响应式布局实现

    • 使用FlexGrid容器配合mediaQuery实现自适应
    • 通过状态变量控制布局切换:
    [@Component](/user/Component)
    struct MediaContainer {
      @State layoutType: LayoutType = LayoutType.SINGLE_FULL;
    
      build() {
        Flex({ direction: this.layoutType === LayoutType.SINGLE_FULL ? 
              FlexDirection.Column : FlexDirection.Row }) {
          // 主画面
          MediaPlayer()
            .width(this.layoutType === LayoutType.SINGLE_FULL ? '100%' : '70%')
          
          // 画中画(横屏时显示)
          if (this.layoutType === LayoutType.DUAL_PIP) {
            MediaPlayer()
              .width('30%')
              .position({ x: '70%', y: 0 })
          }
        }
      }
    }
    
  3. 优化建议

    • aboutToAppear中获取初始方向:display.getDefaultDisplaySync().orientation
    • 使用mediaQuery设置断点:(orientation: landscape)(orientation: portrait)
    • 通过window.getWindowProperties().orientation兼容API 12+版本

此方案通过监听系统方向变化触发状态更新,利用ArkTS声明式UI自动重组布局,实现横竖屏的无缝切换。

回到顶部