鸿蒙一次开发,多端部署(八)典型布局场景

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

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

页签栏

布局效果

实现方案

不同断点下,页签在页面中的位置及尺寸都有差异,可以结合响应式布局能力,设置不同断点下 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)

布局效果

实现方案

运营横幅通常使用 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)!)}
}

网格

布局效果

实现方案

不同断点下,页面中图片的排布不同,此场景可以通过响应式布局能力结合 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%')}
}

侧边栏

布局效果

实现方案

侧边栏通常通过 SideBarContainer组件 实现,结合响应式布局能力,在不同断点下为SiderBarConContainer组件的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}         })}
}

单/双栏

布局效果

实现方案

单/双栏场景可以使用 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')}
}

三分栏

布局效果

场景说明

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

实现方案

三分栏场景可以组合使用 SideBarContainer 组件与 Navigation组件 实现,SideBarContainer组件可以通过侧边栏控制按钮控制显示/隐藏,Navigation组件可以根据窗口宽度自动切换该组件内单/双栏显示,结合响应式布局能力,在不同断点下为SiderBarConContainer组件的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.icon')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.icon') })Item({ label: 'B2', imageSrc: $r('app.media.icon') })}}.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)}
}

自定义弹窗

布局效果

实现方案

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

参考代码:

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

操作入口

布局效果

实现方案

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)}
}

顶部

布局效果

实现方案

最外层使用栅格行组件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 })}
}

缩进布局

布局效果

实现方案

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

参考代码

@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 })}
}

挪移布局

布局效果

实现方案

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

参考代码

@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')}
}

重复布局

布局效果

实现方案

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

参考代码

@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)}
}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761052.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JS】替换文本为emjio表情

最终效果展示 T1 T2 T3 T4 需求 把评论你好帅啊啊啊[开心][开心]&#xff0c;[开心] 替换为图片 思路 正则match提取[开心]到一个数组数组去重创建img标签img标签转文本. 。例&#xff1a;&#xff08;el.outerHTML&#xff09;&#xff0c;将el元素转文本字符串replaceAll…

js【详解】深拷贝

什么是深拷贝&#xff1f; 对于引用类型的数据&#xff0c;才有深浅拷贝的说法 浅拷贝 &#xff1a;执行拷贝的变量只复制被拷贝变量内存的引用数据的地址。 被拷贝变量内地址指向的数据发生变化时&#xff0c;执行拷贝的变量也会同步改变 深拷贝&#xff1a; 在堆内存中开…

如何在edge上安装拓展weTab

1.点解管理拓展 2.点击获取拓展 3.搜索框输入"wetab"并搜索 4.点击获取按钮 5.点击之后跳出弹窗,点击"添加拓展" 6.回到拓展页面,找到wetab拓展,点击右侧启动拓展 7.打开新的界面,wetab已经启动 8.自定义界面 1. 右键图标可以进行删除操作 2.左下角有个设…

Kubernetes集群搭建 kubernetes集群安装

Kubeadm kubeadm 是 Kubernetes 社区提供的集群构建工具&#xff0c;它能够以最佳实践的方式部署一个最小化的可用 Kubernetes 集群。 但是 kubeadm 在设计上并未安装网络解决方案&#xff0c;所以需要用户自行安装第三方符合 CNI 的网络解决方案&#xff0c;如 flanal&#…

【Python + Django】启动简单的文本页面

前言&#xff1a; 为了应付&#xff08;bushi&#xff09;毕业论文&#xff0c;总要自己亲手搞一个像模像样的项目出来吧 ~ ~ 希望自己能在新的连载中学到项目搭建的知识&#xff0c;这也算是为自己的测试经历增添光彩吧&#xff01;&#xff01;&#xff01; 希望、希望大家…

你的电脑打不开摄像头问题

我一直以为我电脑上的摄像头老是打不开是因为硬件不匹配的问题。知道我发现了我的拯救者Y7000的机身盘边的“摄像头开关”按钮。。。 我去&#xff0c;你的摄像头开关按钮怎么设置在机身旁边啊。。。。 —————————————————————— 2024年3月21日更新记录&a…

UE5.3 StateTree使用实践

近期浏览UE的CitySample&#xff08;黑客帝国Demo&#xff09;&#xff0c;发现有不少逻辑用到了StateTree学习一下&#xff0c;StateTree是多层状态机实现&#xff0c;以组件的形式直接挂载在蓝图中运行。 与平时常见的一些FSM库不同&#xff0c;StateTree并不会返回给外界当…

【Git】第二课:git安装和配置

安装 我们伟大祖国有句古话叫巧妇难为无米之炊&#xff0c;还有句话叫工欲善其事必先利其器。所以&#xff0c;在正式的学习之前&#xff0c;我们需要先把git这把利器安装好。 Windows系统 下载安装包 打开Git - Downloading Package页面&#xff0c;根据系统类型32位还是6…

基于C/C++的easyx图形库教程

文章目录: 一&#xff1a;前言 二&#xff1a;窗口&#xff08;宽高 背景颜色 窗口标题 弹出对话框&#xff09; 三&#xff1a;图形绘制&#xff08;点 线 矩形 圆 椭圆&#xff09; 四&#xff1a;文字&#xff08;颜色 大小 背景 位置 打印 文字居中&#xff09; 五&a…

vue3 reactive丢失响应式

问题 使用 reactive 构造响应式对象时&#xff0c;当对其进行重新赋值后&#xff0c;会导致原有变量失去响应式&#xff0c;页面不会发生联动更新 例如&#xff1a; 1、使用 reactive 定义一个响应式的对象变量 let data1 reactive({name: 小李,date: 2024-03-18,address: xx…

解读“CFMS中国闪存市场峰会”存储技术看点-1

昨天CFMS中国闪存市场峰会在深圳举行&#xff0c;小编本来计划前往现场参加&#xff0c;但由于有事冲突未能如期前往&#xff0c;非常遗憾&#xff01; 本次峰会的主题是“存储周期激发潜能”。在闪存市场的供需关系逐渐恢复正常的阶段&#xff0c;闪存市场如何发展变化&#x…

Vue字符串里的中文数字转换为阿拉伯数字

js字符串里的汉字数字转化为数字 <template><view><view><view class"inpbox" ><textarea v-model"voiceMane" input"convert" ></textarea></view></view></view> </template> &…

【视频异常检测】Delving into CLIP latent space for Video Anomaly Recognition 论文阅读

Delving into CLIP latent space for Video Anomaly Recognition 论文阅读 ABSTRACT1. Introduction2. Related Works3. Proposed approach3.1. Selector model3.2. Temporal Model3.3. Predictions Aggregation3.4. Training 4. Experiments4.1. Experiment Setup4.2. Evaluat…

自然语言处理实战项目28-RoBERTa模型在BERT的基础上的改进与架构说明,RoBERTa模型的搭建

大家好,我是微学AI,今天给大家介绍下自然语言处理实战项目28-RoBERTa模型在BERT的基础上的改进与架构说明,RoBERTa模型的搭建。在BERT的基础上,RoBERTa进行了深度优化和改进,使其在多项NLP任务中取得了卓越的成绩。接下来,我们将详细了解RoBERTa的原理、架构以及它在BERT…

ensp不同vlan间的互相通信

关于不同vlan之间的通信&#xff0c;本章做了最简洁的案例&#xff0c;表示说明 1. 网段设置 1.1 划分四个不同 的 vlan vlan网段vlan10192.168.10.254 /24vlan20192.168.20.254 /24vlan30192.168.30.254 /24vlan40192.168.40.254 /24 1.2 SW1的配置 #进入视图 sys #更改交…

QT的学习

代码练习 做一个UI登录页面 源文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("洛克启动&#xff01;");this->…

iOS报错-Command PhaseScriptExecution failed with a nonzero exit code

问题&#xff1a;iOS debug没问题&#xff0c;一打包就报错&#xff1a; Command PhaseScriptExecution failed with a nonzero exit code 解决方法如下&#xff1a; 在项目的Pods目录下&#xff0c;找到Targets Support Files->Pods-xxxx-frameworks.sh 如下&#xff1a…

全域电商数据实现高效稳定大批量采集♀

全域电商&#xff0c;是近几年的新趋势&#xff0c;几乎所有商家都在布局全域&#xff0c;追求全域增长。但商家发现&#xff0c;随着投入成本的上涨&#xff0c;利润却没有增加。 其中最为突出的是——商家为保证全域数据的及时更新&#xff0c;通过堆人头的方式完成每日取数任…

hcip复习总结1

OSI----------- 定义了数据的产生标准 。 7 层 应用 ------- 表示 会话 传输 -----Telnet - 23 ssh---22 http---80 https-443 TCP ---- 传输控制卋议。是一种面向连接的可靠的传输卋议。 UDP---- 用户数据报卋议。是一种非面向连接的丌可靠传输卋议。 保证可靠性&…

Windows系统搭建web网站并结合内网穿透实现公网访问本地站点

文章目录 使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点&#xff0c;测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中&#xff0c;查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2.2 映…