HarmonyOS :適配折疊屏展開與收起哗咆,實(shí)現(xiàn)單屏與雙屏UI的無縫切換

引言

隨著移動(dòng)設(shè)備技術(shù)不斷革新蜘欲,折疊屏手機(jī)以其獨(dú)特的設(shè)計(jì)和靈活的顯示能力,為用戶帶來了前所未有的視覺體驗(yàn)晌柬。然而姥份,這種新型屏幕形式也對(duì)界面設(shè)計(jì)提出了全新挑戰(zhàn)。如何在用戶展開或收起折疊屏?xí)r年碘,即時(shí)響應(yīng)并做出相應(yīng)的UI調(diào)整澈歉,是確保用戶體驗(yàn)連貫性的關(guān)鍵。

在這樣的背景下屿衅,實(shí)現(xiàn)界面布局在單屏與雙屏模式之間的無縫切換埃难。不僅能讓應(yīng)用在單屏模式下保持簡(jiǎn)潔優(yōu)雅,也能在雙屏模式下提供更廣闊的視野和更豐富的交互體驗(yàn)涤久。

適配前后對(duì)比

  • 適配前不做任何處理涡尘,UI布局會(huì)根據(jù)折疊屏的展開、收起自適應(yīng)屏幕响迂,示例如下:
image.png
  • 適配后考抄,單屏、雙屏各自一套 UI 設(shè)計(jì) 可根據(jù)折疊屏的展開蔗彤、收起兩種狀態(tài)座泳,實(shí)現(xiàn)差異性UI交互設(shè)計(jì),示例如下:
image.png

實(shí)際開發(fā)中幕与,我們可以在同一個(gè)頁面挑势,共用一套數(shù)據(jù),實(shí)現(xiàn)從單屏到雙屏的平滑過渡啦鸣,讓用戶在使用過程中感受到無縫的體驗(yàn)潮饱。

實(shí)時(shí)監(jiān)聽折疊屏狀態(tài)

HarmonyOS NEXT 提供 媒體查詢 (@ohos.mediaquery) 可用于實(shí)現(xiàn)監(jiān)聽折疊屏展開、收起變化诫给。

該方法庫(kù)適用于 鴻蒙APP開發(fā)香拉、元服務(wù)開發(fā),關(guān)鍵代碼如下:

listener = mediaquery.matchMediaSync('(min-width: 400vp)')
onPortrait(mediaQueryResult:mediaquery.MediaQueryResult) {
    if (mediaQueryResult.matches) {
      // 展開態(tài)
    } else {
      // 折疊態(tài)
    }
}    

為了方便中狂,我們可把該能力封裝成工具類:

import mediaquery from '@ohos.mediaquery'

/// 折疊屏幕變化監(jiān)聽
abstract class FoldStatusObserver {
  public static readonly foldStatusKey = 'foldStatusKey'
  private static listener?: mediaquery.MediaQueryListener

  /// 在 MainPage aboutToAppear 生命周期內(nèi)調(diào)用
  public static startListening() {
    FoldStatusObserver.listener = mediaquery.matchMediaSync('screen and (min-width: 400vp)')
    FoldStatusObserver.listener.on('change', FoldStatusObserver.onFoldStatusChange)
  }

  /// 在 MainPage aboutToDisappear 生命周期內(nèi)調(diào)用
  public static stopListening() {
    FoldStatusObserver.listener?.off('change', FoldStatusObserver.onFoldStatusChange)
  }

  /// 在任意 Component 內(nèi)通過使用 `@StorageProp('foldStatusKey') isFold: boolean = false` 進(jìn)行監(jiān)聽變化
  private static onFoldStatusChange(result: mediaquery.MediaQueryResult): void {
    const status = AppStorage.get<boolean>(FoldStatusObserver.foldStatusKey)
    if (result.matches !== status) {
      AppStorage.set(FoldStatusObserver.foldStatusKey, result.matches)
    }
  }
}

export { FoldStatusObserver }

使用方式:

  • 第一步凫碌,在 EntryAbility onCreate 生命周期內(nèi)設(shè)置默認(rèn)值:
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  AppStorage.setOrCreate('foldStatusKey', false)
}
  • 第二步,在 MainPage aboutToAppear胃榕、aboutToDisappear 生命周期內(nèi)開啟盛险、關(guān)閉折疊狀態(tài)監(jiān)聽:
struct MainPage {
  aboutToAppear(): void {
    FoldStatusObserver.startListening()
  }

  aboutToDisappear(): void {
    FoldStatusObserver.stopListening()
  }
}
  • 第三步,在任意 Component 內(nèi)通過使用 @StorageProp('foldStatusKey') isFold 進(jìn)行監(jiān)聽折疊屏狀態(tài)變化
@Component
export struct MyComponent {
  @StorageProp('foldStatusKey') isFold: boolean = false

  build() {
    Stack() {
      if (this.isFold) {
        // 這是折疊屏展開時(shí)UI
      } else {
        // 這是折疊屏收起時(shí)UI
      }
    }
    .width('100%')
    .height('100%')
  }
}

容器封裝與UI自適應(yīng)

封裝一個(gè)響應(yīng)折疊狀態(tài)變化的父容器,我們可以在需要折疊屏差異化設(shè)計(jì)的頁面苦掘,通過使用父容器的方式讓代碼變得更簡(jiǎn)潔换帜。

  • 拋磚引玉,容器封裝:
/// 響應(yīng)折疊屏變化的父容器
@Component
export struct FoldStatusContainer {
  @StorageProp('foldStatusKey') isFold: boolean = false
  // 非折疊狀態(tài) UI 布局
  @BuilderParam unFoldBodyContainer: () => void = this.defaultContainer
  // 折疊狀態(tài) UI 布局
  @BuilderParam foldBodyContainer: () => void = this.defaultContainer

  @Builder
  defaultContainer() {
    Column()
  }

  build() {
    Stack() {
      if (this.isFold) {
        this.foldBodyContainer()
      } else {
        this.unFoldBodyContainer()
      }
    }
    .width('100%')
    .height('100%')
  }
}
  • 在需要折疊屏差異化UI設(shè)計(jì)的頁面里鹤啡,我們這樣使用 FoldStatusContainer :
// 適配折疊屏示例頁面
@Component
export struct FixFoldUiExample {

  @Builder
  unFoldUiContainer() {
    Stack() {
      Text('我是全屏內(nèi)容').fontSize(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Orange)
  }

  @Builder
  foldUiContainer() {
    Row() {
      Stack() {
        Text('我是左邊的分屏內(nèi)容').fontSize(20)
      }
      .backgroundColor(Color.Grey)
      .height('100%')
      .layoutWeight(1)

      Stack() {
        Text('我是右邊的分屏內(nèi)容').fontSize(20)
      }
      .backgroundColor(Color.Pink)
      .height('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  bodyContainer() {
    FoldStatusContainer({
      unFoldBodyContainer: this.unFoldUiContainer,
      foldBodyContainer: this.foldUiContainer,
    })
  }

  build() {
    NavDestination() {
      Column() {
        this.bodyContainer()
      }
      .height('100%')
      .width('100%')
      .justifyContent(FlexAlign.Center)
    }.title('適配折疊屏')
  }
}

該頁面的效果如下:


image.png

總結(jié)

  • 通過適配折疊屏惯驼,用戶可以在不同形態(tài)下獲得最佳的交互體驗(yàn),無論是單屏還是展開后的雙屏模式递瑰。
  • 折疊屏為UI設(shè)計(jì)提供了新的交互方式祟牲,如拖拽、分屏等抖部,適配后的應(yīng)用可以利用這些特性創(chuàng)造新的用戶交互體驗(yàn)说贝。

更新

API version 12開始,支持在元服務(wù)中使用以下 api 進(jìn)行折疊屏適配

  • 檢查設(shè)備是否可折疊:鏈接
import { display } from '@kit.ArkUI';

let ret: isFoldable = display.isFoldable();
  • 獲取可折疊設(shè)備的當(dāng)前折疊狀態(tài):鏈接
import { display } from '@kit.ArkUI';

let data: display.FoldStatus = display.getFoldStatus();
  • 開啟折疊設(shè)備折疊狀態(tài)變化的監(jiān)聽:鏈接
import { Callback } from '@kit.BasicServicesKit';

let callback: Callback<display.FoldStatus> = (data: display.FoldStatus) => {
    console.info('Listening enabled. Data: ' + JSON.stringify(data));
};
display.on('foldStatusChange', callback);

附注(Example)

Demo 示例已上傳:

GitHub:https://github.com/liyufengrex/HarmonyAtomicService

GitCode:https://gitcode.com/liyufengrex/HarmonyAtomicService

(基于API11開發(fā)您朽,支持NEXT及以上版本運(yùn)行)已上傳可供參考狂丝,包含如下內(nèi)容:

  • 靜態(tài)庫(kù)+動(dòng)態(tài)包+多模塊設(shè)計(jì)
  • 狀態(tài)管理
  • 統(tǒng)一路由管理(router+navPathStack)
  • 網(wǎng)絡(luò)請(qǐng)求、Loading 等工具庫(kù)封裝
  • 自定義組件哗总、自定義彈窗(解耦)
  • EventBus 事件通知
  • 擴(kuò)展修飾器几颜,實(shí)現(xiàn) 節(jié)流、防抖讯屈、權(quán)限申請(qǐng)
  • 動(dòng)態(tài)路由 (navPathStack + 動(dòng)態(tài)import + WrappedBuilder)
  • UI動(dòng)態(tài)節(jié)點(diǎn)操作 (BuilderNode + NodeController)
  • 折疊屏適配示例
  • 組件工廠示例
  • 組件動(dòng)態(tài)屬性設(shè)置示例
  • 云函數(shù)蛋哭、云數(shù)據(jù)庫(kù)使用示例
  • 華為賬號(hào)服務(wù)示例(快速登陸、快速驗(yàn)證手機(jī)號(hào))
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涮母,一起剝皮案震驚了整個(gè)濱河市谆趾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叛本,老刑警劉巖沪蓬,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異来候,居然都是意外死亡跷叉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門营搅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來云挟,“玉大人,你說我怎么就攤上這事转质≡靶溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵休蟹,是天一觀的道長(zhǎng)沸枯。 經(jīng)常有香客問我日矫,道長(zhǎng),這世上最難降的妖魔是什么辉饱? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任搬男,我火速辦了婚禮拣展,結(jié)果婚禮上彭沼,老公的妹妹穿的比我還像新娘。我一直安慰自己备埃,他們只是感情好姓惑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著按脚,像睡著了一般于毙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辅搬,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天唯沮,我揣著相機(jī)與錄音,去河邊找鬼堪遂。 笑死介蛉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溶褪。 我是一名探鬼主播币旧,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼猿妈!你這毒婦竟也來了吹菱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤彭则,失蹤者是張志新(化名)和其女友劉穎鳍刷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俯抖,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡输瓜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚌成。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片前痘。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖担忧,靈堂內(nèi)的尸體忽然破棺而出芹缔,到底是詐尸還是另有隱情,我是刑警寧澤瓶盛,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布最欠,位于F島的核電站示罗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芝硬。R本人自食惡果不足惜蚜点,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拌阴。 院中可真熱鬧绍绘,春花似錦、人聲如沸迟赃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纤壁。三九已至左刽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酌媒,已是汗流浹背欠痴。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秒咨,地道東北人喇辽。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拭荤,于是被迫代替她去往敵國(guó)和親茵臭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容