本文将深入讲解在 HarmonyOS 5 中如何控制应用的横竖屏切换,包括静态配置、动态控制、监听方向变化以及多设备适配的最佳实践。


一、概述

横竖屏切换是移动应用开发中的常见需求。在 HarmonyOS 中,屏幕方向管理涉及多个层面:

  • Ability 配置:通过 module.json5 配置页面的默认方向

  • 窗口管理:通过 window API 动态控制窗口方向

  • 布局适配:根据方向变化动态调整 UI 布局

  • 事件监听:监听方向变化并做出响应

HarmonyOS 5 提供了灵活的屏幕方向管理能力,支持从简单的固定方向到复杂的多设备自适应策略。


二、静态配置屏幕方向

2.1 module.json5 配置

在 module.json5 文件的 abilities 标签中,通过 orientation 属性配置页面的默认方向:

json

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "orientation": "landscape"  // 强制横屏
      }
    ]
  }
}

2.2 orientation 可选值

说明
unspecified 系统自动判断(默认)
portrait 固定竖屏
landscape 固定横屏
auto_rotation 自动旋转
auto_rotation_restricted 受限自动旋转
landscape_inverted 反向横屏
portrait_inverted 反向竖屏

2.3 应用场景

  • 视频播放器:全局设置为 landscape,强制横屏播放

  • 阅读类应用:全局设置为 portrait,保持竖屏阅读

  • 通用应用:设置为 unspecified,允许系统自动适配


三、动态控制横竖屏切换

3.1 核心 API

使用 window.setPreferredOrientation() 方法动态控制窗口方向:

typescript

import { window } from '@kit.ArkUI';

// 获取窗口对象
let windowStage = AppStorage.get('windowStage') as window.WindowStage;
let mainWindow = windowStage.getMainWindowSync();

// 切换为横屏
mainWindow.setPreferredOrientation(window.Orientation.LANDSCAPE);

// 切换为竖屏
mainWindow.setPreferredOrientation(window.Orientation.PORTRAIT);

// 开启自动旋转
mainWindow.setPreferredOrientation(window.Orientation.AUTO_ROTATION);

3.2 在页面中控制方向

在 ArkTS 页面中,通过 onPageShow 或 aboutToAppear 生命周期控制方向:

typescript

import { window } from '@kit.ArkUI';

@Entry
@Component
struct VideoPlayerPage {
  private windowStage: window.WindowStage = 
    AppStorage.get('windowStage') as window.WindowStage;
  private mainWindow: window.Window = this.windowStage.getMainWindowSync();

  onPageShow() {
    // 进入页面时强制横屏
    this.mainWindow.setPreferredOrientation(window.Orientation.LANDSCAPE);
  }

  onPageHide() {
    // 离开页面时恢复竖屏
    this.mainWindow.setPreferredOrientation(window.Orientation.PORTRAIT);
  }

  build() {
    // 页面内容
  }
}

3.3 使用回调处理结果

typescript

try {
  mainWindow.setPreferredOrientation(
    window.Orientation.AUTO_ROTATION,
    (err: BusinessError) => {
      if (err.code) {
        console.error('设置方向失败: ' + JSON.stringify(err));
        return;
      }
      console.info('设置方向成功');
    }
  );
} catch (exception) {
  console.error('设置方向异常: ' + JSON.stringify(exception));
}

四、监听横竖屏变化

4.1 方法一:使用媒体查询(推荐)

mediaquery API 是监听屏幕方向变化的标准方式:

typescript

import { mediaquery } from '@kit.ArkUI';

@Entry
@Component
struct ScreenRotationPage {
  @State isLandscape: boolean = false;
  private listener?: mediaquery.MediaQueryListener;

  aboutToAppear() {
    // 创建横屏监听器
    this.listener = mediaquery.matchMediaSync('(orientation: landscape)');
    
    // 注册回调
    this.listener.on('change', (result: mediaquery.MediaQueryResult) => {
      this.isLandscape = result.matches;
      console.log(`屏幕方向: ${this.isLandscape ? '横屏' : '竖屏'}`);
    });
  }

  aboutToDisappear() {
    // 移除监听器
    if (this.listener) {
      this.listener.off('change');
    }
  }

  build() {
    Column() {
      Text(this.isLandscape ? '当前为横屏' : '当前为竖屏')
        .fontSize(20)
    }
    .width('100%')
    .height('100%')
  }
}

4.2 方法二:监听窗口尺寸变化

通过监听窗口尺寸变化判断横竖屏状态:

typescript

import { window } from '@kit.ArkUI';

@Entry
@Component
struct SizeChangePage {
  @State isLandscape: boolean = false;
  private windowStage: window.WindowStage = 
    AppStorage.get('windowStage') as window.WindowStage;
  private mainWindow: window.Window = this.windowStage.getMainWindowSync();

  aboutToAppear() {
    // 注册窗口尺寸变化监听
    this.mainWindow.on('windowSizeChange', (data: window.Size) => {
      // 判断宽高比
      const width = px2vp(data.width);
      const height = px2vp(data.height);
      this.isLandscape = width > height;
    });
  }

  aboutToDisappear() {
    this.mainWindow.off('windowSizeChange');
  }

  build() {
    // 根据 isLandscape 渲染不同布局
  }
}

4.3 方法三:在 Ability 层监听

在 UIAbility 中通过 onConfigurationChanged 监听配置变化:

typescript

import { UIAbility, Configuration } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onConfigurationChanged(config: Configuration) {
    // 检查方向变化
    if (config.orientation === Configuration.Orientation.LANDSCAPE) {
      console.log('切换到横屏');
    } else if (config.orientation === Configuration.Orientation.PORTRAIT) {
      console.log('切换到竖屏');
    }
  }
}

五、响应式布局适配

5.1 根据方向动态调整布局

typescript

@Entry
@Component
struct NewsListPage {
  @State isLandscape: boolean = false;
  private listener?: mediaquery.MediaQueryListener;

  aboutToAppear() {
    this.listener = mediaquery.matchMediaSync('(orientation: landscape)');
    this.listener.on('change', (result) => {
      this.isLandscape = result.matches;
    });
  }

  aboutToDisappear() {
    this.listener?.off('change');
  }

  build() {
    Column() {
      // 动态网格:横屏双列,竖屏单列
      Grid() {
        ForEach(this.newsData, (item: NewsItem) => {
          GridItem() {
            NewsCard({ news: item })
          }
        })
      }
      .columnsTemplate(this.isLandscape ? '1fr 1fr' : '1fr')
      .rowsGap(10)
      .columnsGap(10)
    }
  }
}

5.2 使用 GridContainer 实现断点适配

typescript

GridContainer() {
  // 内容
}
.columnsTemplate(this.isLandscape ? '1fr 1fr 1fr' : '1fr 1fr')

六、多设备横竖屏适配策略

6.1 设备类型判断

不同设备需要不同的横竖屏策略:

typescript

import { deviceInfo } from '@kit.BasicServicesKit';

// 判断设备类型
if (deviceInfo.deviceType === 'tablet') {
  // 平板:支持自动旋转
  mainWindow.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
} else {
  // 手机:默认竖屏
  mainWindow.setPreferredOrientation(window.Orientation.PORTRAIT);
}

6.2 基于窗口尺寸的策略

推荐使用窗口尺寸判断是否支持旋转:

typescript

const BREAKPOINT_MD = 600;

function getWindowSize(): window.Rect {
  let windowRect = mainWindow.getWindowProperties().windowRect;
  return windowRect;
}

function shouldSupportRotation(): boolean {
  let rect = getWindowSize();
  let widthVp = px2vp(rect.width);
  let heightVp = px2vp(rect.height);
  let aspectRatio = heightVp / widthVp;
  
  // 条件1:窗口最小边 >= 600vp(大屏设备)
  if (Math.min(widthVp, heightVp) >= BREAKPOINT_MD) {
    return true;
  }
  
  // 条件2:类方屏(宽高比在 0.8~1.2 之间)
  if (aspectRatio >= 0.8 && aspectRatio < 1.2) {
    return true;
  }
  
  // 条件3:平板设备
  if (deviceInfo.deviceType === 'tablet') {
    return true;
  }
  
  return false;  // 默认竖屏
}

6.3 完整的多设备适配方案

在 UIAbility 中实现完整的旋转策略:

typescript

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { deviceInfo } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  private windowObj?: window.Window;

  setDefaultOrientation(): void {
    const BREAKPOINT_MD = 600;
    let windowRect = this.windowObj!.getWindowProperties().windowRect;
    let windowWidthVp = px2vp(windowRect.width);
    let windowHeightVp = px2vp(windowRect.height);
    let aspectRatio = windowHeightVp / windowWidthVp;

    // 判断是否支持旋转
    if (
      Math.min(windowWidthVp, windowHeightVp) >= BREAKPOINT_MD ||
      (aspectRatio >= 0.8 && aspectRatio < 1.2) ||
      deviceInfo.deviceType === 'tablet'
    ) {
      // 支持自动旋转
      this.windowObj?.setPreferredOrientation(
        window.Orientation.AUTO_ROTATION_RESTRICTED
      );
    } else {
      // 竖屏显示
      this.windowObj?.setPreferredOrientation(
        window.Orientation.PORTRAIT
      );
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.getMainWindow().then((windowObj) => {
      this.windowObj = windowObj;
      this.setDefaultOrientation();
      
      // 监听窗口尺寸变化
      this.windowObj.on('windowSizeChange', () => {
        this.setDefaultOrientation();
      });
    });
    
    windowStage.loadContent('pages/Index');
  }
}

七、注意事项与最佳实践

7.1 状态保存与恢复

横竖屏切换时,页面会重新渲染,需要使用 @State 保存状态:

typescript

@Entry
@Component
struct MyPage {
  @State videoProgress: number = 0;
  @State currentTab: number = 0;
  
  // @State 装饰的变量在配置变更时会自动保留
}

7.2 性能优化

  • 避免在 onConfigurationChanged 中进行耗时操作

  • 使用 @State 精确控制需要更新的组件

  • 大型列表使用 LazyForEach 实现懒加载

7.3 折叠屏特殊处理

折叠屏设备需要额外关注折叠态变化:

typescript

import { display } from '@kit.ArkUI';

// 检查是否为折叠屏
let isFoldable = display.isFoldable();

// 获取折叠状态
display.getFoldStatus((err, status) => {
  if (status === display.FoldStatus.FOLD_STATUS_EXPANDED) {
    // 展开态:支持横竖屏
  } else if (status === display.FoldStatus.FOLD_STATUS_FOLDED) {
    // 折叠态:类似手机
  }
});

7.4 常见问题

Q1:页面固定了方向,但是切换页面后方向恢复?

A:方向设置是基于窗口的,需要在每个页面的 onPageShow 中重新设置方向。

Q2:监听方向变化不生效?

A:检查是否在 aboutToDisappear 中正确移除监听器,避免内存泄漏。

Q3:横竖屏切换时 UI 闪烁?

A:使用 @State 驱动布局变化,避免在 build 方法中使用复杂的条件判断。


八、总结

方法 适用场景 优先级
module.json5 静态配置 整个页面固定方向 最低
setPreferredOrientation() 动态切换方向 推荐
mediaquery 监听 响应式布局 推荐
窗口尺寸监听 需要获取具体尺寸 备选
Ability 配置变更 系统级监听 深度需求

HarmonyOS 5 提供了完整的横竖屏管理能力,从简单的固定方向到复杂的多设备自适应,开发者可以根据应用需求选择合适的方案。在多设备场景下,建议结合设备类型、窗口尺寸和宽高比综合判断是否支持旋转,以实现最佳的跨端体验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐