引言
隨著移動(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)屏幕响迂,示例如下:
- 適配后考抄,單屏、雙屏各自一套 UI 設(shè)計(jì) 可根據(jù)折疊屏的展開蔗彤、收起兩種狀態(tài)座泳,實(shí)現(xiàn)差異性UI交互設(shè)計(jì),示例如下:
實(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('適配折疊屏')
}
}
該頁面的效果如下:
總結(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))