鴻蒙HarmonyOS NEXT開發(fā):橫豎屏切換開發(fā)實(shí)踐

一、概述

橫豎屏切換功能即實(shí)現(xiàn)應(yīng)用內(nèi)既支持豎屏顯示也支持橫屏顯示的效果。對(duì)于應(yīng)用內(nèi)不同頁面顯示方向不同的情況,需要在應(yīng)用邏輯中,動(dòng)態(tài)修改窗口方向,來實(shí)現(xiàn)該效果,例如包含視頻播放功能的應(yīng)用,首頁內(nèi)容是采用豎屏方式,而視頻詳情頁則采用橫屏方式展示。

本文主要介紹橫豎屏功能的開發(fā)過程中需要關(guān)注的內(nèi)容,包括如下部分:

  • 窗口旋轉(zhuǎn)策略的選擇
  • 常用應(yīng)用類型的橫豎屏開發(fā)
  • 常見的橫豎屏開發(fā)問題

二、窗口旋轉(zhuǎn)說明

目前在HarmonyOS系統(tǒng)中,窗口的旋轉(zhuǎn)形態(tài)包括以下四種,窗口的狀態(tài)對(duì)應(yīng)真機(jī)實(shí)際狀態(tài)如下:

0000000000011111111.20250122160726.84786407298789346456280156549272.png

有兩種設(shè)置窗口旋轉(zhuǎn)策略的方式:

  • 通過module.json5文件中“orientation”字段進(jìn)行設(shè)置

  • 在代碼中通過調(diào)用窗口window的setPreferredOrientation方法進(jìn)行設(shè)置

這兩種方式觸發(fā)設(shè)置旋轉(zhuǎn)的時(shí)機(jī)不同,總的來說,module.json5文件中的字段在窗口啟動(dòng)時(shí)就會(huì)生效,對(duì)于應(yīng)用啟動(dòng)時(shí)就需要設(shè)置橫屏或者豎屏的應(yīng)用,需要進(jìn)行配置。而setPreferredOrientation是在調(diào)用該方法時(shí)進(jìn)行窗口方向的設(shè)置,用于在應(yīng)用啟動(dòng)之后,還需要改變顯示方向的場(chǎng)景。

1、配置module.json5的orientation字段

此字段配置的是應(yīng)用啟動(dòng)時(shí)的窗口顯示狀態(tài),對(duì)于開屏?xí)r就需要以默認(rèn)的橫屏或者豎屏方式顯示,需要在此字段進(jìn)行相應(yīng)的配置:

{
  "module": {
    // ...
    "abilities": [
      {
        "name": "EntryAbility",
        // ...
        "orientation": "portrait"
      }
    ]
  }
}

其支持的參數(shù)可以參考module.json5配置項(xiàng)中orientation字段相關(guān)配置的orientation字段說明:

根據(jù)應(yīng)用默認(rèn)的旋轉(zhuǎn)行為進(jìn)行相應(yīng)的配置:

如果應(yīng)用是豎屏應(yīng)用,建議配置portrait為默認(rèn)旋轉(zhuǎn)策略。
如果應(yīng)用是橫屏應(yīng)用,例如游戲類應(yīng)用,進(jìn)入游戲時(shí),默認(rèn)就是橫屏,此時(shí)有兩種情況:

  • 僅支持橫屏,建議配置landscape為默認(rèn)旋轉(zhuǎn)策略。

  • 支持在橫屏和反向橫屏中切換,建議設(shè)置為auto_rotation_landscape。

如果應(yīng)用為可旋轉(zhuǎn)應(yīng)用,建議應(yīng)用配置auto_rotation_restricted為默認(rèn)旋轉(zhuǎn)策略。
如果一個(gè)應(yīng)用,在直板機(jī)和折疊機(jī)折疊態(tài)是豎屏應(yīng)用,在平板和折疊機(jī)展開態(tài)默認(rèn)是可旋轉(zhuǎn)應(yīng)用,推薦配置follow_desktop為默認(rèn)旋轉(zhuǎn)策略。

注意
對(duì)于需要通過控制中心進(jìn)行旋轉(zhuǎn)鎖定控制的,可以選擇字段后方帶有restricted字段的旋轉(zhuǎn)策略,此字段表示旋轉(zhuǎn)行為受到控制中心按鈕控制,開關(guān)打開情況下,不隨設(shè)備方向旋轉(zhuǎn),關(guān)閉情況下,則會(huì)發(fā)生跟隨設(shè)備旋轉(zhuǎn)。

以如下文件管理應(yīng)用為例,在系統(tǒng)關(guān)閉了旋轉(zhuǎn)鎖定后,應(yīng)用的頁面都會(huì)隨著手機(jī)旋轉(zhuǎn)而發(fā)生展示上的切換,而打開時(shí)則不會(huì)發(fā)生旋轉(zhuǎn)行為,此時(shí)就需要配置為auto_rotation_restricted。

0000000000011111111.20250122160726.83053407324238079543041417050608.jpg

2、調(diào)用窗口的setPreferredOrientation方法

對(duì)于需要進(jìn)入應(yīng)用后,修改應(yīng)用窗口顯示橫豎屏狀態(tài)的情況下,可以調(diào)用setPreferredOrientation 方法進(jìn)行設(shè)置,典型場(chǎng)景如一些視頻類應(yīng)用、圖片類應(yīng)用等。

0000000000011111111.20250122160726.50811229683241670807466341632337.jpg

此類應(yīng)用在進(jìn)入時(shí)為豎屏,而在視頻播放頁面可以顯示為橫屏,則需要支持用戶臨時(shí)修改窗口方向。由于setPreferredOrientation 方法調(diào)用的是窗口的顯示方向,是整個(gè)應(yīng)用窗口級(jí)別都發(fā)生了旋轉(zhuǎn),窗口將一直保持最后一次設(shè)置窗口方向的效果,即使發(fā)生頁面跳轉(zhuǎn)等行為窗口方向也不會(huì)發(fā)生變化。

四、性能優(yōu)化

由于在窗口旋轉(zhuǎn)時(shí),屏幕的尺寸會(huì)發(fā)生變化,界面會(huì)發(fā)生重新布局,為了提高橫豎屏切換時(shí)的流暢度,需要進(jìn)行相應(yīng)的性能優(yōu)化。

1、使用自定義組件凍結(jié)

旋轉(zhuǎn)時(shí),由于整窗一起旋轉(zhuǎn),會(huì)導(dǎo)致頁面重新布局,但是實(shí)際上需要展示的可能只有播放內(nèi)容,對(duì)于其他的組件可以使用自定義組件凍結(jié)功能,避免由于旋轉(zhuǎn)導(dǎo)致的UI更新操作。例如視頻播放底下的詳情內(nèi)容,可能是單獨(dú)的組件。

@Component({ freezeWhenInactive: true })  // 添加自定義組件凍結(jié)功能
struct VideoDetailView {
  build() {
    Scroll() {
      // ... 詳情內(nèi)容
    }
  }
}

2、對(duì)圖片使用autoResize

如果當(dāng)前旋轉(zhuǎn)頁面存在一些圖片,未經(jīng)合理的裁剪,圖片過大,可以對(duì)圖片設(shè)置autoResize屬性,使圖片裁剪到合適的大小進(jìn)行繪制。該屬性的作用是將組件顯示區(qū)域作為繪制的圖源尺寸,可以減少內(nèi)存占用,例如原圖是19201080,但是顯示區(qū)域是200100,則在解碼時(shí)會(huì)降低采樣編碼到200*100尺寸。

@Builder
function ImageItem(imageSrc:ResourceStr) {
  Stack({}) {
    Image(imageSrc)
      .width('100%')
      .height('100%')
      .autoResize(true)  // 對(duì)圖片使用auto_resize屬性
      .borderRadius(8)
      .objectFit(ImageFit.Fill)
      .backgroundColor('#1AFFFFFF')
  }
}

3、排查一些耗時(shí)操作

排查當(dāng)前頁面是否存在一些冗余的OnAreaChange事件、blur模糊或者一些線性變化linearGradient的屬性,這些都比較耗時(shí),可以根據(jù)是否必須使用來決定是否進(jìn)行優(yōu)化。

四、常見場(chǎng)景示例

1、視頻類應(yīng)用橫豎屏開發(fā)

  • 監(jiān)聽窗口變化

由于傳感器變化或者用戶手動(dòng)設(shè)置窗口方向時(shí),窗口的顯示會(huì)發(fā)生變化,對(duì)應(yīng)窗口的尺寸也會(huì)發(fā)生改變,此時(shí)可以通過拿到窗口的寬高,并對(duì)寬高進(jìn)行對(duì)比,判斷當(dāng)前顯示是豎屏還是橫屏狀態(tài),并利用該數(shù)據(jù)對(duì)布局進(jìn)行適配。

監(jiān)聽窗口尺寸的變化可以通過window.on('windowSizeChange')進(jìn)行實(shí)現(xiàn)。具體的措施如下,在需要進(jìn)行橫豎屏切換的頁面進(jìn)行以下窗口的監(jiān)聽,一般建議是在aboutToAppear中執(zhí)行:

aboutToAppear(): void {
  // ...
  this.windowClass.on('windowSizeChange', (size) => {
    // ...
  });
  // ...
}

并在aboutToDisappear中取消監(jiān)聽:

aboutToDisappear(): void {
  // ...
  this.windowClass.off('windowSizeChange');
}

需要注意的是,當(dāng)用戶手動(dòng)觸發(fā)setOrientation設(shè)置為橫屏狀態(tài)時(shí),即使當(dāng)前手機(jī)處于垂直方向,窗口的狀態(tài)也是橫屏方向,即如下所示:

0000000000011111111.20250122160727.45363304847387078329248792949847.png

此時(shí),窗口的寬是豎屏狀態(tài)下的高,高變?yōu)樨Q屏狀態(tài)的寬。所以在監(jiān)聽窗口變化時(shí),可以通過窗口的寬高大小關(guān)系,來確定當(dāng)前窗口的方向,并決定實(shí)際的橫豎屏狀態(tài)。

this.windowClass.on('windowSizeChange', (size) => {
  let viewWidth = px2vp(size.width);
  let viewHeight = px2vp(size.height);

  if (viewWidth > viewHeight) {
    // ...
  } else {
    // ...
  }
});
  • 進(jìn)行布局適配

對(duì)應(yīng)視頻播放這類應(yīng)用,屬于只有播放窗口需要進(jìn)行橫豎屏,所以只需要對(duì)視頻播放的組件內(nèi)容進(jìn)行橫屏并進(jìn)入全屏,所以可以利用UI狀態(tài)更新的特點(diǎn),來讓播窗變?yōu)槿粒瑢⒉ゴ暗某叽缍x為@State狀態(tài),并設(shè)置到Xcomponent組件上。

@State xComponentWidth: number = px2vp(display.getDefaultDisplaySync().width);
@State xComponentHeight: number = px2vp(display.getDefaultDisplaySync().width * this.aspect);

將狀態(tài)變量與播窗綁定。

XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.xComponentController })
  .onLoad(() => {
    // ...
  })
  .width(this.xComponentWidth)
  .height(this.xComponentHeight)

并且在之前監(jiān)聽窗口變化的回調(diào)中,對(duì)XComponentWidth和XComponentHeight進(jìn)行動(dòng)態(tài)修改,完成窗口變化時(shí)橫屏和豎屏的視頻窗口布局。需要注意的是,在橫屏?xí)r,視頻播放的寬高應(yīng)該和窗口的寬高一樣,并且需要進(jìn)入全屏狀態(tài)。而豎屏?xí)r,視頻播放的寬應(yīng)該等于窗口的寬,但是高度應(yīng)該是按照播窗比例乘以窗口的寬進(jìn)行設(shè)置,并退出全屏狀態(tài)。

0000000000011111111.20250122160727.08498987436753709268496965439315.png

具體實(shí)現(xiàn)如下,進(jìn)入視頻詳情頁面內(nèi)需要監(jiān)聽窗口尺寸的變化,并根據(jù)當(dāng)前狀態(tài),實(shí)現(xiàn)對(duì)橫豎屏狀態(tài)的監(jiān)聽,根據(jù)狀態(tài)變化修改對(duì)應(yīng)XComponent的寬高,實(shí)現(xiàn)全屏或者隱藏狀態(tài)欄和導(dǎo)航條的邏輯。

this.windowClass.on('windowSizeChange', (size) => {

  let viewWidth = px2vp(size.width);
  let viewHeight = px2vp(size.height);

  if (viewWidth > viewHeight) {
    // 實(shí)現(xiàn)橫屏邏輯
    if (display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_EXPANDED || display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_HALF_FOLDED) {
      this.xComponentHeight = viewWidth * this.aspect;
      this.xComponentWidth = viewWidth;
    } else {
      this.xComponentWidth = viewHeight / this.aspect;
      this.xComponentHeight = viewHeight;
      this.isLandscape = true;
    }
    this.windowClass.setSpecificSystemBarEnabled('navigationIndicator', false); // hide bottom navigation bar
  } else {
    // 實(shí)現(xiàn)豎屏邏輯
    this.xComponentHeight = viewWidth * this.aspect;
    this.xComponentWidth = viewWidth;

    this.windowClass.setSpecificSystemBarEnabled('navigationIndicator', true); // show bottom navigation bar
    this.isLandscape = false;
  }
});

2、游戲類應(yīng)用橫屏開發(fā)

對(duì)于游戲類應(yīng)用,以橫屏游戲居多,此類應(yīng)用不需要在應(yīng)用內(nèi)進(jìn)行開關(guān)控制,所以只需要在module.json5配置文件中進(jìn)行相應(yīng)的配置即可。一般有以下幾種情況:

  • 默認(rèn)僅為橫屏
    如果該應(yīng)用默認(rèn)為橫屏狀態(tài),那么則需要在module.json5中的“orientation”字段進(jìn)行配置為landscape。
0000000000011111111.20250122160727.56893488200561787782624235833858.png
  • 支持橫屏和反向橫屏
    如果應(yīng)用需要根據(jù)設(shè)備方向,決定是橫屏還是反向橫屏,則可以對(duì)module.json5配置中“orientation”設(shè)置為auto_rotation_landscape。
0000000000011111111.20250122160727.31767448070804627576045424298166.png

注意 如果需要跟隨控制中心的旋轉(zhuǎn)鎖定,則可以選擇配置為"auto_rotation_landscape_restricted"

  • 支持豎屏切換橫屏
    此類適用于一些游戲大廳等應(yīng)用,由于游戲大廳內(nèi),主頁可能為豎屏,部分應(yīng)用為橫屏。那么從豎屏進(jìn)入橫屏?xí)r,則需要調(diào)用設(shè)置窗口旋轉(zhuǎn)方法進(jìn)行窗口控制。

五、其他常見問題

1、Tabs欄中的視頻橫屏播放,無法隱藏Tabs欄

對(duì)于首頁中有部分視頻可以直接播放,并且不會(huì)跳轉(zhuǎn)至詳情頁播放的,需要支持直接在首頁發(fā)生旋轉(zhuǎn),當(dāng)前可以通過設(shè)置XComponent的寬高實(shí)現(xiàn),但是會(huì)發(fā)現(xiàn)即使全屏后,Tabs欄并不會(huì)消失。而是會(huì)一起發(fā)生旋轉(zhuǎn)并存在于頁面上。解決方案如下:

進(jìn)入全屏?xí)r,隱藏Tabs,退出全屏?xí)r,展示Tabs欄。

@Component
struct TabsView {
  @State isLayoutFullScreen:boolean = false;

  build() {
    Tabs() {
      // ... 省略布局內(nèi)容
    }
    .barHeight(this.isLayoutFullScreen ? 0 : 50) // 通過用戶是否需要點(diǎn)擊進(jìn)入全屏,隱藏Tabs標(biāo)簽欄的高度,可以實(shí)現(xiàn)隱藏
  }
}

2、如何解決直板機(jī)和平板上默認(rèn)旋轉(zhuǎn)行為不一致的問題?

對(duì)于部分應(yīng)用,在直板手機(jī)上默認(rèn)是采用豎屏顯示的策略,但是在平板或者折疊屏上,是需要支持自動(dòng)旋轉(zhuǎn)的,如果在Ability的生命周期中調(diào)用setPreferredOrientation,可能會(huì)出現(xiàn)在應(yīng)用啟動(dòng)時(shí),出現(xiàn)旋轉(zhuǎn)動(dòng)畫的情況,所以可以將module.json5文件中的“orientation”字段設(shè)置為“follow_desktop”。

3、通過調(diào)用window.getLastWindow的方式獲取窗口實(shí)例出現(xiàn)延遲,如何解決?

由于getLastWindow底層原因,需要經(jīng)過查找獲取實(shí)例,一定程度上會(huì)有性能損耗,可能會(huì)出現(xiàn)已經(jīng)發(fā)生橫屏或者豎屏切換的情況下,狀態(tài)欄還沒切換的情況。對(duì)此類場(chǎng)景的同步要求較高情況下,可以使用windowStage.getMainWindowSync的同步方式獲取窗口實(shí)例。

4、自動(dòng)旋轉(zhuǎn)和旋轉(zhuǎn)鎖定按鈕的關(guān)系是什么?與Orientation字段的關(guān)系如何判斷?

控制中心的旋轉(zhuǎn)開關(guān)控制的是當(dāng)前是否可以跟隨屏幕旋轉(zhuǎn),“旋轉(zhuǎn)鎖定”高亮狀態(tài)是鎖定,不可旋轉(zhuǎn)?!靶D(zhuǎn)鎖定”灰色是解鎖狀態(tài),可以旋轉(zhuǎn)。對(duì)于希望跟隨控制中心的旋轉(zhuǎn)開關(guān)的行為,可以選擇帶RESTRICTED后綴字段的旋轉(zhuǎn)方式。

例如想實(shí)現(xiàn)跟隨控制中心的自動(dòng)旋轉(zhuǎn),如果想旋轉(zhuǎn)到橫屏,豎屏,反向橫屏,反向豎屏。則可以設(shè)置為AUTO_ROTATION_RESTRICTED。

如果不想跟隨控制中心的控制開關(guān),那么只需要設(shè)置為AUTO_ROTATION,此時(shí)應(yīng)用的旋轉(zhuǎn)不受到控制中心的鎖定控制。其他的旋轉(zhuǎn)方式同理。

其他更詳細(xì)內(nèi)容可參見官網(wǎng)《最佳實(shí)踐》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容