鴻蒙UI開發
本文旨在分享一些鴻蒙UI布局開發上的一些建議,特別是對屏幕寬高比發生變化時的應對思路和好的實踐。
折疊屏適配
一般情況(自適應布局/響應式布局)
1.自適應布局
1.1自適應拉伸

左右組件定寬
TypeScript //左右定寬 Row() { ? Text("健康使用手機") ? ? .fontSize(16) ? ? .width(135) ? Blank() ? Toggle({ type: ToggleType.Switch }) ? ? .width(36) } .borderRadius(12) .padding({ left: 13, right: 13 }) .backgroundColor('#FFFFFF') .width('100%') |
左右組件不定寬(左組件占剩余寬度,右組件不定寬)
TypeScript //layoutWeight //左右不定寬, ? Row() { ? ? ... ? ? Text("我的寬度占剩余的寬度"+"111111111111") ? ? ? .maxLines(1) ? ? ? .textOverflow({ overflow: TextOverflow.Ellipsis }) ? ? ... ? } ? .layoutWeight(1) ? Text("我的寬度不固定") ? ? .textAlign(TextAlign.End) ? ? //這個margin自我調整,一般為做組件所有icon的寬度和 ? ? .margin({ left: 54 }) } .width('100%') |
1.2均分拉伸

靈活使用彈性布局Flex
TypeScript //不換行 Flex({ justifyContent: FlexAlign.SpaceEvenly }) { ? ForEach(this.list, () => { ? ? this.Item() ? }) } //換行 Flex({ justifyContent: FlexAlign.Center ,wrap:FlexWrap.Wrap}) { ? ForEach(this.list, () => { ? ? this.Item() ? }) } |
1.3自適應延伸

TypeScript Scroll() { ? Row({ space: 10 }) { ? ? ForEach(this.appList, () => { ? ? ? Column() { ? ? ? ? Image($r('app.media.icon')) ? ? ? ? ? .width(48) ? ? ? ? ? .height(48) ? ? ? ? Text($r('app.string.show_app_name')) ? ? ? ? ? .fontSize(12) ? ? ? ? ? .textAlign(TextAlign.Center) ? ? ? }.width(80).height(102) ? ? }) ? } } .scrollable(ScrollDirection.Horizontal) .width("100%") |
2.響應式布局
2.1媒體查詢

TypeScript //核心代碼 private breakpoints: BreakpointEntity[] = [ ? { name: 'xs', size: 0 }, { name: 'sm', size: 320 }, ? { name: 'md', size: 600 }, { name: 'lg', size: 840 } ] /** ?* 更新當前斷點 ?* 當傳入的斷點與當前斷點不同時,更新當前斷點并持久化 ?*/ private updateCurrentBreakpoint(breakpoint: string) { ? if (this.currentBreakpoint !== breakpoint) { ? ? this.currentBreakpoint = breakpoint; ? ? AppStorage.Set<string>('currentBreakpoint', this.currentBreakpoint); ? ? console.log('on current breakpoint: ' + this.currentBreakpoint); ? } } /** ?* 注冊斷點監聽器 ?* 為每個斷點創建媒體查詢監聽器,當屏幕尺寸匹配時,更新當前斷點 ?*/ public register() { ? this.breakpoints.forEach((breakpoint: BreakpointEntity, index) => { ? ? let condition :string = ''; ? ? // 監聽句柄,反應視窗寬度和breakpoint.size的關系 ? ? if (index === this.breakpoints.length - 1) { ? ? ? condition = '(' + breakpoint.size + 'vp<=width' + ')'; ? ? } else { ? ? ? condition = '(' + breakpoint.size + 'vp<=width<' + this.breakpoints[index + 1].size + 'vp)'; ? ? } ? ? console.log(condition); ? ? // breakpoint.size vp <= width 的條件改變時觸發回調,傳遞此時視窗大小 ? ? breakpoint.mediaQueryListener = mediaQuery.matchMediaSync(condition); ? ? breakpoint.mediaQueryListener.on('change', (mediaQueryResult) => { ? ? ? if (mediaQueryResult.matches) { ? ? ? ? this.updateCurrentBreakpoint(breakpoint.name); ? ? ? } ? ? }) ? }) } |
3.典型布局場景
3.1挪移布局

柵格布局GridRow
TypeScript GridRow() { ? GridCol({ span: { sm: 12, md: 6, lg: 6 } }) { ? ? Text("圖片內容") ? } ? .width("100%") ? .height("50%") ? .backgroundColor(Color.Red) ? GridCol({ span: { sm: 12, md: 6, lg: 6 } }) { ? ? ? Text("文字標題") ? } ? .width("100%") ? .height("50%") ? .backgroundColor(Color.Blue) } |
3.2重復布局
柵格布局GridRow
TypeScript Scroll() { ? GridRow() { ? ? ForEach([0, 1, 2, 3, 4, 5, 6, 7], () => { ? ? ? GridCol({ span: { sm: 12, md: 6, lg: 6 } }) { ? ? ? ? Column() { ? ? ? ? ? RepeatItemContent() ? ? ? ? } ? ? ? } ? ? }) ? } } |
3.3頂部布局
柵格布局GridRow
TypeScript @State needWrap: boolean = true build() { ? GridRow() { ? ? // 第一行布局 ? ? GridCol({ span: { sm: 12, md: 6, lg: 7 } }) { ? ? ? Row() { ? ? ? ? Text($r('app.string.recommended')).fontSize(24) ? ? ? ? Blank() ? ? ? ? Image($r('app.media.ic_public_more')) ? ? ? ? ? .width(32) ? ? ? ? ? .height(32) ? ? ? ? ? .visibility(this.needWrap ? Visibility.Visible : Visibility.None) ? ? ? } ? ? ? .width('100%') ? ? ? .alignItems(VerticalAlign.Center) ? ? } ? ? // 第二行布局 ? ? GridCol({ span: { sm: 12, md: 6, lg: 5 } }) { ? ? ? Flex({ alignItems: ItemAlign.Center }) { ? ? ? ? Search({ placeholder: '猜您喜歡: 萬水千山' }) ? ? ? ? Image($r('app.media.audio_fm')) ? ? ? ? ? .width(32) ? ? ? ? ? .height(32) ? ? ? ? Image($r('app.media.ic_public_more')) ? ? ? ? ? .width(32) ? ? ? ? ? .height(32) ? ? ? ? ? .visibility(this.needWrap ? Visibility.None : Visibility.Visible) ? ? ? } ? ? }//控制顯隱多余元素 ? }.onBreakpointChange((breakpoint: string) => { ? ? if (breakpoint === 'sm') { ? ? ? this.needWrap = true ? ? } else { ? ? ? this.needWrap = false ? ? } ? }) } |
3.復雜情況(判斷手機的狀態)
3.1折疊屏適配
TypeScript // 當前折疊屏狀態(若當前為折疊屏設備才有效) @State curFoldStatus: display.FoldStatus = display.getFoldStatus(); if (display.isFoldable()) { ? // 監聽折疊屏狀態變更,更新折疊態 ? display.on('foldStatusChange', (curFoldStatus: display.FoldStatus) => { ? ? this.curFoldStatus = curFoldStatus; ? }) } build() { ?? // 折疊屏UI展示 ?? if (display.isFoldable()) { ?? ? ? ... ?? ? } ?? } else { // 非折疊屏UI展示 ?? ? ? ... ?? ? } ?? } } |