Landscape-Aware Half-Modal: Slide In from the Right with bindSheet + CustomDialog
Requirement Description
When using bindSheet, the half-modal sheet always slides up from the bottom. The goal is to keep this behavior in portrait, but—in landscape—open a right-side sliding panel instead.
Background Knowledge
-
bindSheetbinds a half-modal sheet to any component; by default it pops from the bottom. Docs - Use media query to detect orientation changes and switch the presentation.
Implementation Steps
-
Listen for orientation using
this.getUIContext().getMediaQuery().matchMediaSync('(orientation: landscape)')and subscribe to itschangeevent. - Keep a small state machine:
-
showType = 0→ no dialog -
showType = 1→ portrait sheet open (half-modal withbindSheet) -
showType = 2→ landscape panel open (custom dialog on the right)
-
-
Portrait: open the half-modal using
bindSheetandisShow = true. -
Landscape: close the sheet (if any) and open a right-aligned
CustomDialogusingalignment: DialogAlignment.CenterEnd,width: '30%',height: '100%'. - On orientation change, swap: close the currently shown presentation and open the other.
- Clean up listeners in
aboutToDisappear.
Code Snippet / Configuration
import { mediaquery } from '@kit.ArkUI';
@Component
struct MenuExample {
@State list: string[] = ['Share Screen', 'Chat', 'Raise Hand']
build() {
Column() {
Column() {
ForEach(this.list, (item: string) => {
Text(item).fontSize(16).margin({ bottom: 30 })
.fontColor(Color.Black)
})
}
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
// Custom right-side dialog (for landscape)
@CustomDialog
struct CustomDialogExample {
controller?: CustomDialogController
build() {
MenuExample()
}
}
@Entry
@Component
struct SheetTransitionExample {
@State isShow: boolean = false // controls the portrait half-sheet
@State showType: number = 0 // 0: none, 1: portrait sheet open, 2: landscape dialog open
@State matches: boolean = false // false = portrait
// Media query listener (use UIContext API; legacy mediaquery.matchMediaSync is deprecated)
listener = this.getUIContext().getMediaQuery().matchMediaSync('(orientation: landscape)');
dialogController: CustomDialogController | null = new CustomDialogController({
builder: CustomDialogExample(),
onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) {
this.showType = 0
}
dismissDialogAction.dismiss(); // register dismiss action
},
autoCancel: true,
alignment: DialogAlignment.CenterEnd, // right side
cornerRadius: 20,
width: '30%',
height: '100%',
})
// Swap presentation based on current orientation & state
ChangeWindow() {
if (this.showType !== 0) {
if (this.matches) {
// Landscape: close portrait sheet, open right-side dialog
this.isShow = false
if (this.dialogController != null) {
this.dialogController.open()
}
} else {
// Portrait: close landscape dialog, open half-sheet
this.isShow = true
if (this.dialogController != null) {
this.dialogController.close()
}
}
}
}
onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) {
this.matches = mediaQueryResult.matches
this.ChangeWindow()
}
aboutToAppear(): void {
// Register orientation change callback
const portraitFunc = (r: mediaquery.MediaQueryResult): void => this.onPortrait(r)
this.listener.on('change', portraitFunc);
}
aboutToDisappear(): void {
this.listener.off('change') // unregister callback
this.dialogController = null // release dialog controller
}
@Builder
myBuilder() {
MenuExample()
}
build() {
Column() {
Button('Show Panel')
.onClick(() => {
this.showType = this.matches ? 2 : 1
this.ChangeWindow()
})
.fontSize(20)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.bindSheet($$this.isShow, this.myBuilder(), {
height: 300,
onWillDismiss: ((dismissSheetAction: DismissSheetAction) => {
if (dismissSheetAction.reason === DismissReason.SLIDE_DOWN) {
this.showType = 0
}
dismissSheetAction.dismiss(); // register dismiss action
}),
})
}
}
Test Results
- Verified that portrait shows a bottom half-sheet and landscape shows a right-side panel.
- Switching device orientation live swaps between the two presentations without stale overlays.
Limitations or Considerations
-
bindSheetcannot slide from the right; the right-side behavior is implemented viaCustomDialog. - Ensure you close whichever presentation isn’t in use during orientation changes to avoid double overlays.
- For wearables, landscape orientation is uncommon; however, the panel pattern is still useful for side-docked interactions on larger or rotated screens.
Top comments (0)