鸿蒙OS开发:【一次开发,多端部署】(典型布局场景)

2024-05-24 05:52

本文主要是介绍鸿蒙OS开发:【一次开发,多端部署】(典型布局场景),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

典型布局场景

虽然不同应用的页面千变万化,但对其进行拆分和分析,页面中的很多布局场景是相似的。本小节将介绍如何借助自适应布局、响应式布局以及常见的容器类组件,实现应用中的典型布局场景。

布局场景实现方案 开发前请熟悉鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。
[页签栏]Tab组件 + 响应式布局
[运营横幅(Banner)]Swiper组件 + 响应式布局
[网格]Grid组件 / List组件 + 响应式布局
[侧边栏]SideBar组件 + 响应式布局
[单/双栏]Navigation组件 + 响应式布局
[三分栏]SideBar组件 + Navigation组件 + 响应式布局
[自定义弹窗]CustomDialogController组件 + 响应式布局
[大图浏览]Image组件
[操作入口]Scroll组件+Row组件横向均分
[顶部]栅格组件
[缩进布局]栅格组件
[挪移布局]栅格组件
[重复布局]栅格组件

说明:  在本文[媒体查询]小节中已经介绍了如何通过媒体查询监听断点变化,后续的示例中不再重复介绍此部分代码。

页签栏

image.png
布局效果

实现方案

不同断点下,页签在页面中的位置及尺寸都有差异,可以结合响应式布局能力,设置不同断点下[Tab组件]的barPosition、vertical、barWidth和barHeight属性实现目标效果。

另外,页签栏中的文字和图片的相对位置不同,同样可以通过设置不同断点下[tabBar]对应的CustomBuilder中的布局方向,实现目标效果。

参考代码

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'interface TabBar  {name: stringicon: ResourceselectIcon: Resource
}@Entry
@Component
struct Home {@State currentIndex: number = 0@State tabs: Array<TabBar> = [{name: '首页',icon: $r('app.media.ic_music_home'),selectIcon: $r('app.media.ic_music_home_selected')}, {name: '排行榜',icon: $r('app.media.ic_music_ranking'),selectIcon: $r('app.media.ic_music_ranking_selected')}, {name: '我的',icon: $r('app.media.ic_music_me_nor'),selectIcon: $r('app.media.ic_music_me_selected')}]@Builder TabBarBuilder(index: number, tabBar: TabBar) {Flex({direction: new BreakPointType({sm: FlexDirection.Column,md: FlexDirection.Row,lg: FlexDirection.Column}).getValue(this.currentBreakpoint),justifyContent: FlexAlign.Center,alignItems: ItemAlign.Center}) {Image(this.currentIndex === index ? tabBar.selectIcon : tabBar.icon).size({ width: 36, height: 36 })Text(tabBar.name).fontColor(this.currentIndex === index ? '#FF1948' : '#999').margin(new BreakPointType<(Length|Padding)>({sm: { top: 4 },md: { left: 8 },lg: { top: 4 } }).getValue(this.currentBreakpoint)!).fontSize(16)}.width('100%').height('100%')}@StorageLink('currentBreakpoint') currentBreakpoint: string = 'md'private breakpointSystem: BreakpointSystem = new BreakpointSystem()aboutToAppear() {this.breakpointSystem.register()}aboutToDisappear() {this.breakpointSystem.unregister()}build() {Tabs({barPosition: new BreakPointType({sm: BarPosition.End,md: BarPosition.End,lg: BarPosition.Start}).getValue(this.currentBreakpoint)}) {ForEach(this.tabs, (item:TabBar, index) => {TabContent() {Stack() {Text(item.name).fontSize(30)}.width('100%').height('100%')}.tabBar(this.TabBarBuilder(index!, item))})}.vertical(new BreakPointType({ sm: false, md: false, lg: true }).getValue(this.currentBreakpoint)!).barWidth(new BreakPointType({ sm: '100%', md: '100%', lg: '96vp' }).getValue(this.currentBreakpoint)!).barHeight(new BreakPointType({ sm: '72vp', md: '56vp', lg: '60%' }).getValue(this.currentBreakpoint)!).animationDuration(0).onChange((index: number) => {this.currentIndex = index})}
}

运营横幅(Banner)

image.png
布局效果

实现方案

运营横幅通常使用[Swiper组件]实现。不同断点下,运营横幅中展示的图片数量不同。只需要结合响应式布局,配置不同断点下Swiper组件的displayCount属性,即可实现目标效果。

参考代码

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'@Entry
@Component
export default struct Banner {private data: Array<Resource> = [$r('app.media.banner1'),$r('app.media.banner2'),$r('app.media.banner3'),$r('app.media.banner4'),$r('app.media.banner5'),$r('app.media.banner6'),]private breakpointSystem: BreakpointSystem = new BreakpointSystem()@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'aboutToAppear() {this.breakpointSystem.register()}aboutToDisappear() {this.breakpointSystem.unregister()}build() {Swiper() {ForEach(this.data, (item:Resource) => {Image(item).size({ width: '100%', height: 200 }).borderRadius(12).padding(8)})}.indicator(new BreakPointType({ sm: true, md: false, lg: false }).getValue(this.currentBreakpoint)!).displayCount(new BreakPointType({ sm: 1, md: 2, lg: 3 }).getValue(this.currentBreakpoint)!)}
}

网格

image.png
布局效果

实现方案

不同断点下,页面中图片的排布不同,此场景可以通过响应式布局能力结合[Grid组件]实现,通过调整不同断点下的Grid组件的columnsTemplate属性即可实现目标效果。

另外,由于本例中各列的宽度相同,也可以通过响应式布局能力结合[List组件]实现,通过调整不同断点下的List组件的lanes属性也可实现目标效果。

参考代码

通过Grid组件实现

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'interface GridItemInfo {name: stringimage: Resource
}@Entry
@Component
struct MultiLaneList {private data: GridItemInfo[] = [{ name: '歌单集合1', image: $r('app.media.1') },{ name: '歌单集合2', image: $r('app.media.2') },{ name: '歌单集合3', image: $r('app.media.3') },{ name: '歌单集合4', image: $r('app.media.4') },{ name: '歌单集合5', image: $r('app.media.5') },{ name: '歌单集合6', image: $r('app.media.6') },{ name: '歌单集合7', image: $r('app.media.7') },{ name: '歌单集合8', image: $r('app.media.8') },{ name: '歌单集合9', image: $r('app.media.9') },{ name: '歌单集合10', image: $r('app.media.10') },{ name: '歌单集合11', image: $r('app.media.11') },{ name: '歌单集合12', image: $r('app.media.12') }]private breakpointSystem: BreakpointSystem = new BreakpointSystem()@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'aboutToAppear() {this.breakpointSystem.register()}aboutToDisappear() {this.breakpointSystem.unregister()}build() {Grid() {ForEach(this.data, (item: GridItemInfo) => {GridItem() {Column() {Image(item.image).aspectRatio(1.8)Text(item.name).margin({ top: 8 }).fontSize(20)}.padding(4)}})}.columnsTemplate(new BreakPointType({sm: '1fr 1fr',md: '1fr 1fr 1fr 1fr',lg: '1fr 1fr 1fr 1fr 1fr 1fr'}).getValue(this.currentBreakpoint)!)}
}

通过List组件实现

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'interface ListItemInfo {name: stringimage: Resource
}@Entry
@Component
struct MultiLaneList {private data: ListItemInfo[] = [{ name: '歌单集合1', image: $r('app.media.1') },{ name: '歌单集合2', image: $r('app.media.2') },{ name: '歌单集合3', image: $r('app.media.3') },{ name: '歌单集合4', image: $r('app.media.4') },{ name: '歌单集合5', image: $r('app.media.5') },{ name: '歌单集合6', image: $r('app.media.6') },{ name: '歌单集合7', image: $r('app.media.7') },{ name: '歌单集合8', image: $r('app.media.8') },{ name: '歌单集合9', image: $r('app.media.9') },{ name: '歌单集合10', image: $r('app.media.10') },{ name: '歌单集合11', image: $r('app.media.11') },{ name: '歌单集合12', image: $r('app.media.12') }]private breakpointSystem: BreakpointSystem = new BreakpointSystem()@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'aboutToAppear() {this.breakpointSystem.register()}aboutToDisappear() {this.breakpointSystem.unregister()}build() {List() {ForEach(this.data, (item: ListItemInfo) => {ListItem() {Column() {Image(item.image)Text(item.name).margin({ top: 8 }).fontSize(20)}.padding(4)}})}.lanes(new BreakPointType({ sm: 2, md: 4, lg: 6 }).getValue(this.currentBreakpoint)!).width('100%')}
}

侧边栏

布局效果

image.png

实现方案

侧边栏通常通过[SideBarContainer组件]实现,结合响应式布局能力,在不同断点下为SideBarConContainer组件的sideBarWidth、showControlButton等属性配置不同的值,即可实现目标效果。

参考代码

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'interface imagesInfo{label:string,imageSrc:Resource
}
const images:imagesInfo[]=[{label:'moon',imageSrc:$r('app.media.my_image_moon')},{label:'sun',imageSrc:$r('app.media.my_image')}
]@Entry
@Component
struct SideBarSample {@StorageLink('currentBreakpoint') private currentBreakpoint: string = "md";private breakpointSystem: BreakpointSystem = new BreakpointSystem()@State selectIndex: number = 0;@State showSideBar:boolean=false;aboutToAppear() {this.breakpointSystem.register() }aboutToDisappear() {this.breakpointSystem.unregister()}@Builder itemBuilder(index: number) {Text(images[index].label).fontSize(24).fontWeight(FontWeight.Bold).borderRadius(5).margin(20).backgroundColor('#ffffff').textAlign(TextAlign.Center).width(180).height(36).onClick(() => {this.selectIndex = index;if(this.currentBreakpoint === 'sm'){this.showSideBar=false}})}build() {SideBarContainer(this.currentBreakpoint === 'sm' ? SideBarContainerType.Overlay : SideBarContainerType.Embed) {Column() {this.itemBuilder(0)this.itemBuilder(1)}.backgroundColor('#F1F3F5').justifyContent(FlexAlign.Center)Column() {Image(images[this.selectIndex].imageSrc).objectFit(ImageFit.Contain).height(300).width(300)}.justifyContent(FlexAlign.Center).width('100%').height('100%')}.height('100%').sideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%').minSideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%').maxSideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%').showControlButton(this.currentBreakpoint === 'sm').autoHide(false).showSideBar(this.currentBreakpoint !== 'sm'||this.showSideBar).onChange((isBarShow: boolean) => {if(this.currentBreakpoint === 'sm'){this.showSideBar=isBarShow}         })}
}

单/双栏

布局效果

image.png

实现方案

单/双栏场景可以使用[Navigation组件]实现,Navigation组件可以根据窗口宽度自动切换单/双栏显示,减少开发工作量。

参考代码

@Component
struct Details {private imageSrc: Resource=$r('app.media.my_image_moon')build() {Column() {Image(this.imageSrc).objectFit(ImageFit.Contain).height(300).width(300)}.justifyContent(FlexAlign.Center).width('100%').height('100%')}
}@Component
struct Item {private imageSrc?: Resourceprivate label?: stringbuild() {NavRouter() {Text(this.label).fontSize(24).fontWeight(FontWeight.Bold).borderRadius(5).backgroundColor('#FFFFFF').textAlign(TextAlign.Center).width(180).height(36)NavDestination() {Details({imageSrc: this.imageSrc})}.title(this.label).backgroundColor('#FFFFFF')}}
}@Entry
@Component
struct NavigationSample {build() {Navigation() {Column({space: 30}) {Item({label: 'moon', imageSrc: $r('app.media.my_image_moon')})Item({label: 'sun', imageSrc: $r('app.media.my_image')})}.justifyContent(FlexAlign.Center).height('100%').width('100%')}.mode(NavigationMode.Auto).backgroundColor('#F1F3F5').height('100%').width('100%').navBarWidth(360).hideToolBar(true).title('Sample')}
}

三分栏

布局效果

image.png

场景说明

为充分利用设备的屏幕尺寸优势,应用在大屏设备上常常有二分栏或三分栏的设计,即“A+C”,“B+C”或“A+B+C”的组合,其中A是侧边导航区,B是列表导航区,C是内容区。在用户动态改变窗口宽度时,当窗口宽度大于或等于840vp时页面呈现A+B+C三列,放大缩小优先变化C列;当窗口宽度小于840vp大于等于600vp时呈现A+C列,放大缩小时优先变化C列;当窗口宽度小于600vp大于等于360vp时,仅呈现C列。

实现方案

三分栏场景可以组合使用[SideBarContainer]组件与[Navigation组件]实现,SideBarContainer组件可以通过侧边栏控制按钮控制显示/隐藏,Navigation组件可以根据窗口宽度自动切换该组件内单/双栏显示,结合响应式布局能力,在不同断点下为SideBarConContainer组件的minContentWidth属性配置不同的值,即可实现目标效果。设置minContentWidth属性的值可以通过[断点]监听窗口尺寸变化的同时设置不同的值并储存成一个全局对象。

参考代码

// MainAbility.ts
import window from '@ohos.window'
import display from '@ohos.display'
import Ability from '@ohos.app.ability.Ability'export default class MainAbility extends Ability {private windowObj?: window.Windowprivate curBp?: stringprivate myWidth?: number// ...// 根据当前窗口尺寸更新断点private updateBreakpoint(windowWidth:number) :void{// 将长度的单位由px换算为vplet windowWidthVp = windowWidth / (display.getDefaultDisplaySync().densityDPI / 160)let newBp: string = ''let newWd: numberif (windowWidthVp < 320) {newBp = 'xs'newWd = 360} else if (windowWidthVp < 600) {newBp = 'sm'newWd = 360} else if (windowWidthVp < 840) {newBp = 'md'newWd = 600} else {newBp = 'lg'newWd = 600}if (this.curBp !== newBp) {this.curBp = newBpthis.myWidth = newWd// 使用状态变量记录当前断点值AppStorage.setOrCreate('currentBreakpoint', this.curBp)// 使用状态变量记录当前minContentWidth值AppStorage.setOrCreate('myWidth', this.myWidth)}}onWindowStageCreate(windowStage: window.WindowStage) :void{windowStage.getMainWindow().then((windowObj) => {this.windowObj = windowObj// 获取应用启动时的窗口尺寸this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width)// 注册回调函数,监听窗口尺寸变化windowObj.on('windowSizeChange', (windowSize)=>{this.updateBreakpoint(windowSize.width)})});// ...}// 窗口销毁时,取消窗口尺寸变化监听onWindowStageDestroy() :void {if (this.windowObj) {this.windowObj.off('windowSizeChange')}}//...
}// tripleColumn.ets
@Component
struct Details {private imageSrc: Resource=$r('app.media.startIcon')build() {Column() {Image(this.imageSrc).objectFit(ImageFit.Contain).height(300).width(300)}.justifyContent(FlexAlign.Center).width('100%').height('100%')}
}@Component
struct Item {private imageSrc?: Resourceprivate label?: stringbuild() {NavRouter() {Text(this.label).fontSize(24).fontWeight(FontWeight.Bold).backgroundColor('#66000000').textAlign(TextAlign.Center).width('100%').height('30%')NavDestination() {Details({imageSrc: this.imageSrc})}.title(this.label).hideTitleBar(false).backgroundColor('#FFFFFF')}.margin(10)}
}@Entry
@Component
struct TripleColumnSample {@State arr: number[] = [1, 2, 3]@StorageProp('myWidth') myWidth: number = 360@Builder NavigationTitle() {Column() {Text('Sample').fontColor('#000000').fontSize(24).width('100%').height('100%').align(Alignment.BottomStart).margin({left:'5%'})}.alignItems(HorizontalAlign.Start)}build() {SideBarContainer() {Column() {List() {ForEach(this.arr, (item:number, index) => {ListItem() {Text('A'+item).width('100%').height("20%").fontSize(24).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).backgroundColor('#66000000')}})}.divider({ strokeWidth: 5, color: '#F1F3F5' })}.width('100%').height('100%').justifyContent(FlexAlign.SpaceEvenly).backgroundColor('#F1F3F5')Column() {Navigation() {List(){ListItem() {Column() {Item({ label: 'B1', imageSrc: $r('app.media.startIcon') })Item({ label: 'B2', imageSrc: $r('app.media.startIcon') })}}.width('100%')}}.mode(NavigationMode.Auto).minContentWidth(360).navBarWidth(240).backgroundColor('#FFFFFF').height('100%').width('100%').hideToolBar(true).title(this.NavigationTitle)}.width('100%').height('100%')}.sideBarWidth(240).minContentWidth(this.myWidth)}
}

自定义弹窗

image.png

布局效果

实现方案

自定义弹窗通常通过[CustomDialogController]实现,有两种方式实现本场景的目标效果:

  • 通过gridCount属性配置自定义弹窗的宽度。

    系统默认对不同断点下的窗口进行了栅格化:sm断点下为4栅格,md断点下为8栅格,lg断点下为12栅格。通过gridCount属性可以配置弹窗占据栅格中的多少列,将该值配置为4即可实现目标效果。

  • 将customStyle设置为true,即弹窗的样式完全由开发者自定义。

    开发者自定义弹窗样式时,开发者可以根据需要配置弹窗的宽高和背景色(非弹窗区域保持默认的半透明色)。自定义弹窗样式配合[栅格组件]同样可以实现目标效果。

参考代码

@Entry
@Component
struct CustomDialogSample {// 通过gridCount配置弹窗的宽度dialogControllerA: CustomDialogController = new CustomDialogController({builder: CustomDialogA ({cancel: this.onCancel,confirm: this.onConfirm}),cancel: this.onCancel,autoCancel: true,gridCount: 4,customStyle: false})// 自定义弹窗样式dialogControllerB: CustomDialogController = new CustomDialogController({builder: CustomDialogB ({cancel: this.onCancel,confirm: this.onConfirm}),cancel: this.onCancel,autoCancel: true,customStyle: true})onCancel() {console.info('callback when dialog is canceled')}onConfirm() {console.info('callback when dialog is confirmed')}build() {Column() {Button('CustomDialogA').margin(12).onClick(() => {this.dialogControllerA.open()})Button('CustomDialogB').margin(12).onClick(() => {this.dialogControllerB.open()})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}
}@CustomDialog
struct CustomDialogA {controller?: CustomDialogControllercancel?: () => voidconfirm?: () => voidbuild() {Column() {Text('是否删除此联系人?').fontSize(16).fontColor('#E6000000').margin({bottom: 8, top: 24, left: 24, right: 24})Row() {Text('取消').fontColor('#007DFF').fontSize(16).layoutWeight(1).textAlign(TextAlign.Center).onClick(()=>{if(this.controller){this.controller.close()}this.cancel!()})Line().width(1).height(24).backgroundColor('#33000000').margin({left: 4, right: 4})Text('删除').fontColor('#FA2A2D').fontSize(16).layoutWeight(1).textAlign(TextAlign.Center).onClick(()=>{if(this.controller){this.controller.close()}this.confirm!()})}.height(40).margin({left: 24, right: 24, bottom: 16})}.borderRadius(24)}
}@CustomDialog
struct CustomDialogB {controller?: CustomDialogControllercancel?: () => voidconfirm?: () => voidbuild() {GridRow({columns: {sm: 4, md: 8, lg: 12}}) {GridCol({span: 4, offset: {sm: 0, md: 2, lg: 4}}) {Column() {Text('是否删除此联系人?').fontSize(16).fontColor('#E6000000').margin({bottom: 8, top: 24, left: 24, right: 24})Row() {Text('取消').fontColor('#007DFF').fontSize(16).layoutWeight(1).textAlign(TextAlign.Center).onClick(()=>{if(this.controller){this.controller.close()}this.cancel!()})Line().width(1).height(24).backgroundColor('#33000000').margin({left: 4, right: 4})Text('删除').fontColor('#FA2A2D').fontSize(16).layoutWeight(1).textAlign(TextAlign.Center).onClick(()=>{if(this.controller){this.controller.close()}this.confirm!()})}.height(40).margin({left: 24, right: 24, bottom: 16})}.borderRadius(24).backgroundColor('#FFFFFF')}}.margin({left: 24, right: 24})}
}

大图浏览

布局效果

image.png

实现方案

图片通常使用[Image组件]展示,Image组件的objectFit属性默认为ImageFit.Cover,即保持宽高比进行缩小或者放大以使得图片两边都大于或等于显示边界。在大图浏览场景下,因屏幕与图片的宽高比可能有差异,常常会发生图片被截断的问题。此时只需将Image组件的objectFit属性设置为ImageFit.Contain,即保持宽高比进行缩小或者放大并使得图片完全显示在显示边界内,即可解决该问题。

参考代码

@Entry
@Component
struct BigImage {build() {Row() {Image($r("app.media.image")).objectFit(ImageFit.Contain)}}
}

操作入口

布局效果

image.png

实现方案

Scroll(内容超出宽度时可滚动) + Row(横向均分:justifyContent(FlexAlign.SpaceAround)、 最小宽度约束:constraintSize({ minWidth: ‘100%’ })

参考代码

interface OperationItem {name: stringicon: Resource
}@Entry
@Component
export default struct OperationEntries {@State listData: Array<OperationItem> = [{ name: '私人FM', icon: $r('app.media.self_fm') },{ name: '歌手', icon: $r('app.media.singer') },{ name: '歌单', icon: $r('app.media.song_list') },{ name: '排行榜', icon: $r('app.media.rank') },{ name: '热门', icon: $r('app.media.hot') },{ name: '运动音乐', icon: $r('app.media.sport') },{ name: '音乐FM', icon: $r('app.media.audio_fm') },{ name: '福利', icon: $r('app.media.bonus') }]build() {Scroll() {Row() {ForEach(this.listData, (item:OperationItem) => {Column() {Image(item.icon).width(48).aspectRatio(1)Text(item.name).margin({ top: 8 }).fontSize(16)}.justifyContent(FlexAlign.Center).height(104).padding({ left: 12, right: 12 })})}.constraintSize({ minWidth: '100%' }).justifyContent(FlexAlign.SpaceAround)}.width('100%').scrollable(ScrollDirection.Horizontal)}
}

顶部

布局效果

image.png

实现方案

最外层使用栅格行组件GridRow布局

文本标题使用栅格列组件GridCol

搜索框使用栅格列组件GridCol

参考代码

@Entry
@Component
export default struct Header {@State needWrap: boolean = truebuild() {GridRow() {GridCol({ span: { sm: 12, md: 6, lg: 7 } }) {Row() {Text('推荐').fontSize(24)Blank()Image($r('app.media.ic_public_more')).width(32).height(32).objectFit(ImageFit.Contain).visibility(this.needWrap ? Visibility.Visible : Visibility.None)}.width('100%').height(40).alignItems(VerticalAlign.Center)}GridCol({ span: { sm: 12, md: 6, lg: 5 } }) {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {Search({ placeholder: '猜您喜欢: 万水千山' }).placeholderFont({ size: 16 }).margin({ top: 4, bottom: 4 })Image($r('app.media.audio_fm')).width(32).height(32).objectFit(ImageFit.Contain).flexShrink(0).margin({ left: 12 })Image($r('app.media.ic_public_more')).width(32).height(32).objectFit(ImageFit.Contain).flexShrink(0).margin({ left: 12 }).visibility(this.needWrap ? Visibility.None : Visibility.Visible)}}}.onBreakpointChange((breakpoint: string) => {if (breakpoint === 'sm') {this.needWrap = true} else {this.needWrap = false}}).padding({ left: 12, right: 12 })}
}

缩进布局

布局效果

image.png

实现方案

借助[栅格组件],控制待显示内容在不同的断点下占据不同的列数,即可实现不同设备上的缩进效果。另外还可以调整不同断点下栅格组件与两侧的间距,获得更好的显示效果。

参考代码

@Entry
@Component
struct IndentationSample {@State private gridMargin: number = 24build() {Row() {GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: 24}) {GridCol({span: {sm: 4, md: 6, lg: 8}, offset: {md: 1, lg: 2}}) {Column() {ForEach([0, 1, 2, 4], () => {Column() {ItemContent()}})}.width('100%')}}.margin({left: this.gridMargin, right: this.gridMargin}).onBreakpointChange((breakpoint: string) => {if (breakpoint === 'lg') {this.gridMargin = 48} else if (breakpoint === 'md') {this.gridMargin = 32} else {this.gridMargin = 24}})}.height('100%').alignItems((VerticalAlign.Center)).backgroundColor('#F1F3f5')}
}@Component
struct ItemContent {build() {Column() {Row() {Row() {}.width(28).height(28).borderRadius(14).margin({ right: 15 }).backgroundColor('#E4E6E8')Row() {}.width('30%').height(20).borderRadius(4).backgroundColor('#E4E6E8')}.width('100%').height(28)Row() {}.width('100%').height(68).borderRadius(16).margin({ top: 12 }).backgroundColor('#E4E6E8')}.height(128).borderRadius(24).backgroundColor('#FFFFFF').padding({ top: 12, bottom: 12, left: 18, right: 18 }).margin({ bottom: 12 })}
}

挪移布局

布局效果

image.png

实现方案

不同断点下,栅格子元素占据的列数会随着开发者的配置发生改变。当一行中的列数超过栅格组件在该断点的总列数时,可以自动换行,即实现”上下布局”与”左右布局”之间切换的效果。

参考代码

@Entry
@Component
struct DiversionSample {@State private currentBreakpoint: string = 'md'@State private imageHeight: number = 0build() {Row() {GridRow() {GridCol({span: {sm: 12, md: 6, lg: 6}}) {Image($r('app.media.illustrator')).aspectRatio(1).onAreaChange((oldValue: Area, newValue: Area) => {this.imageHeight = Number(newValue.height)}).margin({left: 12, right: 12})}GridCol({span: {sm: 12, md: 6, lg: 6}}) {Column(){Text($r('app.string.user_improvement')).textAlign(TextAlign.Center).fontSize(20).fontWeight(FontWeight.Medium)Text($r('app.string.user_improvement_tips')).textAlign(TextAlign.Center).fontSize(14).fontWeight(FontWeight.Medium)}.margin({left: 12, right: 12}).justifyContent(FlexAlign.Center).height(this.currentBreakpoint === 'sm' ? 100 : this.imageHeight)}}.onBreakpointChange((breakpoint: string) => {this.currentBreakpoint = breakpoint;})}.height('100%').alignItems((VerticalAlign.Center)).backgroundColor('#F1F3F5')}
}

重复布局

布局效果

image.png
实现方案

不同断点下,配置栅格子组件占据不同的列数,即可实现“小屏单列显示、大屏双列显示”的效果。另外,还可以通过栅格组件的onBreakpointChange事件,调整页面中显示的元素数量。

参考代码

搜狗高速浏览器截图20240326151344.png

`HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿`@Entry
@Component
struct RepeatSample {@State private currentBreakpoint: string = 'md'@State private listItems: number[] = [1, 2, 3, 4, 5, 6, 7, 8]@State private gridMargin: number = 24build() {Row() {// 当目标区域不足以显示所有元素时,可以通过上下滑动查看不同的元素Scroll() {GridRow({gutter: 24}) {ForEach(this.listItems, () => {// 通过配置元素在不同断点下占的列数,实现不同的布局效果GridCol({span: {sm: 12, md: 6, lg: 6}}) {Column() {RepeatItemContent()}}})}.margin({left: this.gridMargin, right: this.gridMargin}).onBreakpointChange((breakpoint: string) => {this.currentBreakpoint = breakpoint;if (breakpoint === 'lg') {this.gridMargin = 48} else if (breakpoint === 'md') {this.gridMargin = 32} else {this.gridMargin = 24}})}.height(348)}.height('100%').backgroundColor('#F1F3F5')}
}@Component
struct RepeatItemContent {build() {Flex() {Row() {}.width(43).height(43).borderRadius(12).backgroundColor('#E4E6E8').flexGrow(0)Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.SpaceAround }) {Row() {}.height(10).width('80%').backgroundColor('#E4E6E8')Row() {}.height(10).width('50%').backgroundColor('#E4E6E8')}.flexGrow(1).margin({ left: 13 })}.padding({ top: 13, bottom: 13, left: 13, right: 37 }).height(69).backgroundColor('#FFFFFF').borderRadius(24)}
}

这篇关于鸿蒙OS开发:【一次开发,多端部署】(典型布局场景)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/997555

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

闲置电脑也能活出第二春?鲁大师AiNAS让你动动手指就能轻松部署

对于大多数人而言,在这个“数据爆炸”的时代或多或少都遇到过存储告急的情况,这使得“存储焦虑”不再是个别现象,而将会是随着软件的不断臃肿而越来越普遍的情况。从不少手机厂商都开始将存储上限提升至1TB可以见得,我们似乎正处在互联网信息飞速增长的阶段,对于存储的需求也将会不断扩大。对于苹果用户而言,这一问题愈发严峻,毕竟512GB和1TB版本的iPhone可不是人人都消费得起的,因此成熟的外置存储方案开

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

poj2505(典型博弈)

题意:n = 1,输入一个k,每一次n可以乘以[2,9]中的任何一个数字,两个玩家轮流操作,谁先使得n >= k就胜出 这道题目感觉还不错,自己做了好久都没做出来,然后看了解题才理解的。 解题思路:能进入必败态的状态时必胜态,只能到达胜态的状态为必败态,当n >= K是必败态,[ceil(k/9.0),k-1]是必胜态, [ceil(ceil(k/9.0)/2.0),ceil(k/9.

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta