HarmonyOS鸿蒙Next中如何判断当前是全屏还是分屏,根据屏幕不同展示不同的UI

HarmonyOS鸿蒙Next中如何判断当前是全屏还是分屏,根据屏幕不同展示不同的UI

如何监听窗口大小的变化,判断当前是全屏还是分屏,根据屏幕不同展示不同的UI

8 回复

【问题分析】

楼主想要监听屏幕的状态变化可以通过媒体查询和窗口状态监听来实现

【解决方案】

关于窗口状态监听其他的楼主已经给出了相应的建议,推荐媒体查询是因为这是一个全局的实现,一个应用里面存在多个窗口时就需要监听多个窗口,mediaQueryListener.on(‘change’),能够实现监听设备的屏幕状态,根据返回的状态值来适配不同状态的UI布局

public register() {
  this.breakpoints.forEach((breakpoint: Breakpoint, index) => {
    let condition: string
    if (index === this.breakpoints.length - 1) {
      condition = `(${breakpoint.size}vp<=width)`
    } else {
      condition = `(${breakpoint.size}vp<=width<${this.breakpoints[index + 1].size}vp)`
    }
    breakpoint.mediaQueryListener = mediaQuery.matchMediaSync(condition)
    if (breakpoint.mediaQueryListener.matches) {
      this.updateCurrentBreakpoint(breakpoint.name)
    }
    breakpoint.mediaQueryListener.on('change', (mediaQueryResult) => {
      if (mediaQueryResult.matches) {
        this.updateCurrentBreakpoint(breakpoint.name)
      }
    })
  })
}

【代码示例】

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

@Entry
@Component
struct MediaQueryExample {
  @State color: string = '#DB7093'
  @State text: string = 'Portrait'
  listener: mediaquery.MediaQueryListener = this.getUIContext().getMediaQuery().matchMediaSync('(orientation: landscape)'); //监听横屏事件,mediaquery.matchMediaSync接口已废弃,建议使用this.getUIContext().getMediaQuery().matchMediaSync()来获取

  onPortrait(mediaQueryResult:mediaquery.MediaQueryResult) {
    if (mediaQueryResult.matches) {
      this.color = '#FFD700'
      this.text = 'Landscape'
    } else {
      this.color = '#DB7093'
      this.text = 'Portrait'
    }
  }

  aboutToAppear() {
    let portraitFunc = (mediaQueryResult: mediaquery.MediaQueryResult): void => this.onPortrait(mediaQueryResult)
    // 绑定回调函数
    this.listener.on('change', portraitFunc);
  }

  aboutToDisappear() {
    // 解绑listener中注册的回调函数
    this.listener.off('change');
  }

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Text(this.text).fontSize(24).fontColor(this.color)
    }
    .width('100%').height('100%')
  }
}

更多关于HarmonyOS鸿蒙Next中如何判断当前是全屏还是分屏,根据屏幕不同展示不同的UI的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


一、监听窗口大小变化

通过注册 windowSizeChange 事件监听窗口尺寸变化,获取实时窗口尺寸(单位:px):

// 在UIAbility的onWindowStageCreate中设置监听
import { window } from '@kit.ArkUI';
import { UIAbility } from '@kit.AbilityKit';

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

  onWindowStageCreate(windowStage: window.WindowStage) {
    // 获取主窗口
    windowStage.getMainWindow((err, data) => {
      if (err) {
        console.error('Failed to get main window. Cause: ' + JSON.stringify(err));
        return;
      }
      this.windowObj = data;
      
      // 注册窗口尺寸变化监听
      this.windowObj.on('windowSizeChange', (size: window.Size) => {
        console.info('Window size changed: ' + JSON.stringify(size));
        // 这里可触发UI更新逻辑
        this.updateUIByWindowSize(size);
      });
    });
  }

  // 窗口销毁时取消监听
  onWindowStageDestroy() {
    if (this.windowObj) {
      this.windowObj.off('windowSizeChange');
    }
  }
}

二、判断全屏/分屏状态

通过 windowStatusChange 事件监听窗口模式变化,直接获取当前状态(全屏、分屏、最大化等):

// 注册窗口状态监听
this.windowObj.on('windowStatusChange', (status: window.WindowStatusType) => {
  console.info('Window status changed: ' + JSON.stringify(status));
  
  // 判断全屏/分屏(需根据API版本适配)
  if (status === window.WindowStatusType.FULL_SCREEN) {
    console.info('当前为全屏模式');
    this.handleFullScreenUI();
  } else if (status === window.WindowStatusType.SPLIT_SCREEN) {
    console.info('当前为分屏模式');
    this.handleSplitScreenUI();
  } else if (status === window.WindowStatusType.MAXIMIZE) {
    // API version 14+:最大化模式(2合1设备)
    console.info('当前为最大化模式(非全屏)');
    this.handleMaximizeUI();
  }
});

三、根据屏幕差异展示不同UI

方案1:基于断点(Breakpoint)的响应式布局

根据窗口宽度(转换为vp单位)划分断点,动态调整UI:

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

// 定义断点(单位:vp)
private updateBreakpoint(windowWidthPx: number) {
  const density = display.getDefaultDisplaySync().densityPixels;
  const windowWidthVp = windowWidthPx / density; // 转换为vp
  
  let breakpoint: string;
  if (windowWidthVp < 320) {
    breakpoint = 'xs'; // 智能穿戴等小屏设备
  } else if (windowWidthVp < 600) {
    breakpoint = 'sm'; // 手机等中等屏幕
  } else if (windowWidthVp < 840) {
    breakpoint = 'md'; // 平板等大屏
  } else {
    breakpoint = 'lg'; // 大屏设备
  }
  
  // 存储断点信息供UI使用(例如通过AppStorage)
  AppStorage.setOrCreate('currentBreakpoint', breakpoint);
}

// 在windowSizeChange回调中调用
private updateUIByWindowSize(size: window.Size) {
  this.updateBreakpoint(size.width);
  // 同时可结合窗口状态综合判断
  this.windowObj.getWindowStatus().then((status) => {
    this.applyUILayout(status, size);
  });
}

方案2:直接结合窗口状态和尺寸

private applyUILayout(status: window.WindowStatusType, size: window.Size) {
  if (status === window.WindowStatusType.FULL_SCREEN) {
    // 全屏模式下使用紧凑布局
    this.loadLayout('fullscreen_layout');
  } else if (status === window.WindowStatusType.SPLIT_SCREEN) {
    // 分屏模式下使用双栏布局
    this.loadLayout('split_screen_layout');
  } else {
    // 根据宽度动态选择布局
    if (size.width < 600) { // 窄屏布局
      this.loadLayout('compact_layout');
    } else { // 宽屏布局
      this.loadLayout('expanded_layout');
    }
  }
}

四、完整示例代码(UIAbility中)

import { UIAbility } from '@kit.AbilityKit';
import { window, display } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

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

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.getMainWindow((err: BusinessError, data) => {
      if (err) {
        console.error('Failed to get main window. Cause: ' + JSON.stringify(err));
        return;
      }
      this.windowObj = data;

      // 1. 监听窗口尺寸变化
      this.windowObj.on('windowSizeChange', (size: window.Size) => {
        this.updateBreakpoint(size.width);
      });

      // 2. 监听窗口状态变化
      this.windowObj.on('windowStatusChange', (status: window.WindowStatusType) => {
        this.handleWindowStatusChange(status);
      });

      // 初始获取一次状态
      this.windowObj.getWindowStatus().then((status) => {
        this.handleWindowStatusChange(status);
      });
    });
  }

  private updateBreakpoint(windowWidthPx: number) {
    // ... 断点逻辑同上文
  }

  private handleWindowStatusChange(status: window.WindowStatusType) {
    // ... 状态处理逻辑同上文
  }

  onWindowStageDestroy() {
    if (this.windowObj) {
      this.windowObj.off('windowSizeChange');
      this.windowObj.off('windowStatusChange');
    }
  }
}

【问题背景】:如何区分全屏状态还是分屏状态,做UI适配

【解决思路】:主要可以通过窗口的on(‘windowSizeChange’)方法实现对窗口尺寸大小变化的监听。再根据窗口的尺寸变化,更新调整自身应用布局以实现适配。

主要步骤和示例如下:

  1. 在onWindowStageCreate方法中,获取Window对象。
  2. 通过getWindowProperties方法返回值中的windowRect获取窗口尺寸,写入AppStorage中用于UI侧窗口尺寸的首次初始化赋值。
  3. 使用on(‘windowSizeChange’)注册窗口尺寸变化时的监听,并写入AppStorage中供UI侧布局使用。
  4. UI侧通过@StorageLink绑定窗口尺寸后,AppStorage中属性key值对应的数据一旦改变,UI侧会同步修改。
  5. @StorageLink装饰的数据本身是状态变量,所以窗口尺寸发生变化时,会引起组件的重新渲染,开发者可以根据最新的窗口尺寸动态调整应用布局。
// EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    console.info('Ability onWindowStageCreate.');
    windowStage.getMainWindow().then((windowClass) => {
      // 获取窗口尺寸,存入AppStorage
      AppStorage.setOrCreate('winWidth', windowClass.getWindowProperties().windowRect.width);
      AppStorage.setOrCreate('winHeight', windowClass.getWindowProperties().windowRect.height);
      // 监听窗口尺寸变化
      windowClass.on('windowSizeChange', (windowSize) => {
        AppStorage.setOrCreate('winWidth', windowSize.width);
        AppStorage.setOrCreate('winHeight', windowSize.height);
      });
    });
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error('Failed to load the content. Cause: ' + JSON.stringify(err));
        return;
      }
      console.info('Succeeded in loading the content.');
    });
  }
}

页面入口UI文件

// Index.ets
@Entry
@Component
struct Index {
  // 初始化参数,这里会初始化为AppStorage中存储的值
  [@StorageLink](/user/StorageLink)('winWidth') winWidth: number = 1260;
  [@StorageLink](/user/StorageLink)('winHeight') winHeight: number = 2224;

  aboutToAppear() {
    console.info('Current window size. width: ' + this.winWidth + ', height: ' + this.winHeight);
  }

  build() {
    Row() {
      // 根据winWidth、winHeight动态调整应用布局
      // ...
    }
    .size({
      width: this.getUIContext().px2vp(this.winWidth),
      height: this.getUIContext().px2vp(this.winHeight)
    })
  }
}

具体可以参考以下的官方文档说明

应用布局适配智慧多窗

1.在EntryAbility的onWindowStageCreate里增加监听屏幕大小变化,并用AppStorage.setOrCreate缓存起来,使用emitter.emit发送消息推送:

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.getMainWindowSync().on('windowSizeChange', (windowSize) => {
    //缓存最新的屏幕宽高
    AppStorage.setOrCreate('screenWidth', windowSize.width);
    AppStorage.setOrCreate('screenHeight', windowSize.height);

    //发送消息推送
    emitter.emit({
      eventId: 100,
      priority: emitter.EventPriority.IMMEDIATE
    });
  }
}

2.在需要实时接收屏幕大小变化的Page的aboutToAppear里使用emitter.on绑定消息回调,回调中通过AppStorage.get获取最新缓存起来的屏幕大小值,然后更新当前page组件绑定的@State数据:

@Entry
@Component
struct MyPage {
  [@State](/user/State) buttonWidth: number = 200;

  aboutToAppear(): void {
    //绑定消息回调
    emitter.on({eventId: 100}, (event) => {
      //从AppStorage读取最新的屏幕宽高
      let screenWidth: number = AppStorage.get('screenWidth') as number;
      let screenHeight: number = AppStorage.get('screenHeight') as number;

      //更新组件对应的数据
      if(screenWidth < screenHeight) {
        this.buttonWidth = 200;
      } else {
        this.buttonWidth = 400;
      }
    }
  }

  build() {
    Column() {
      Button('按钮')
      .width(this.buttonWidth)
    }
    .width('100%')
    .height('100%')
  }
}

望采纳!!!

监听窗口大小变化:

window.WindowStage.getMainWindow().then((windowClass) => { // 获取窗口尺寸 const width = px2vp(windowClass.getWindowProperties().windowRect.width); const height = px2vp(windowClass.getWindowProperties().windowRect.height); // 监听窗口尺寸变化 windowClass.on(‘windowSizeChange’, (windowSize) => { const width = px2vp(windowSize.width); const height = px2vp(windowSize.height); }); });

【问题现象】

如何监听当前窗口模式,例如全屏状态、分屏模式,针对不同模式进行适配。

【解决方案】

在EntryAbility的onWindowStageCreate方法中使用on(‘windowStatusChange’)监听窗口模式,示例代码如下:

onWindowStageCreate(windowStage: window.WindowStage): void {
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
  let windowClass : window.Window | undefined = undefined;
  AppStorage.setOrCreate('windowStatus', window.WindowStatusType.FULL_SCREEN)
  windowStage.getMainWindow((err, data) => {
    if (err.code) {
      hilog.error(DOMAIN, 'testTag', 'Failed to obtain the main window. Cause: %{public}s', `error: ${err.message}`);
      return;
    }
    windowClass = data;
    windowClass.on("windowStatusChange", (windowStatusType) => {
      AppStorage.setOrCreate('windowStatus', windowStatusType)
    })
  })
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', `error: ${err.message}`);
      return;
    }
    hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
  });
}

在Index.ets中根据不同的窗口模式显示不同的文字,示例代码如下:

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

@Entry
@Component
struct Index {
  @State message: string = '';
  @StorageProp('windowStatus') windowStatus:number = window.WindowStatusType.FULL_SCREEN
  build() {
    RelativeContainer() {
      if (this.windowStatus === window.WindowStatusType.FULL_SCREEN) {
        Text('FULL SCREEN')
          .id('HelloWorld')
          .fontSize($r('app.float.page_text_font_size'))
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
      } else if (this.windowStatus === window.WindowStatusType.SPLIT_SCREEN) {
        Text('SPLIT SCREEN')
          .id('HelloWorld')
          .fontSize($r('app.float.page_text_font_size'))
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
      } else {
        Text('OTHER STATUS')
          .id('HelloWorld')
          .fontSize($r('app.float.page_text_font_size'))
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
      }
    }
    .height('100%')
    .width('100%')
  }
}

注意:在自由窗口状态下,应用的targetAPIVersion设置小于14时,在窗口最大化状态(窗口铺满整个屏幕,2in1设备会有dock栏和状态栏,Tablet设备会有状态栏)时返回值对应为WindowStatusType::FULL_SCREEN。

应用的targetAPIVersion设置大于等于14时,在窗口最大化状态(窗口铺满整个屏幕,2in1设备会有dock栏和状态栏,Tablet设备会有状态栏)时返回值对应为WindowStatusType::MAXIMIZE。

Q:PC窗口最小化不触发onBackground,如何获取最小化的事件?

A:使用窗口实例的on(‘windowStatusChange’)方法开启窗口模式变化的监听。

在HarmonyOS Next中,可通过windowMode属性判断窗口模式。使用Window类的getWindowMode()方法获取当前状态,返回值为全屏(WINDOW_MODE_FULLSCREEN)或分屏(WINDOW_MODE_SPLIT_PRIMARYWINDOW_MODE_SPLIT_SECONDARY)。根据返回值动态调整UI布局与组件可见性,实现不同屏幕模式下的界面适配。

在HarmonyOS Next中,可以通过Window类的on('windowSizeChange')事件监听窗口大小变化,结合windowSizeClass判断当前显示模式(全屏/分屏),动态调整UI布局。

具体实现步骤:

  1. 获取窗口实例并监听尺寸变化
import { window } from '@kit.ArkUI';

// 获取当前窗口
let currentWindow = window.getLastWindow(this.context);

// 注册窗口大小变化监听
currentWindow.on('windowSizeChange', (windowSize) => {
  // 根据窗口尺寸更新UI
  this.updateLayout(windowSize);
});
  1. 根据窗口尺寸分类判断显示模式
updateLayout(windowSize: window.Size) {
  // 使用窗口尺寸分类标准
  if (windowSize.width >= 840) {  // 大屏设备全屏模式
    this.showFullScreenUI();
  } else if (windowSize.width >= 600) {  // 分屏模式
    this.showSplitScreenUI(); 
  } else {  // 小屏模式
    this.showCompactUI();
  }
}
  1. 在UI中使用状态变量控制布局
@State currentLayout: string = 'compact';

// 根据不同布局条件渲染UI
build() {
  if (this.currentLayout === 'fullScreen') {
    // 全屏布局组件
  } else if (this.currentLayout === 'splitScreen') {
    // 分屏布局组件  
  } else {
    // 紧凑布局组件
  }
}

通过这种方式,应用可以实时响应窗口尺寸变化,在不同显示模式下提供最优的用户界面体验。记得在组件销毁时取消事件监听,避免内存泄漏。

回到顶部