典型布局場景
雖然不同應用的頁面千變萬化,但對其進行拆分和分析,頁面中的很多布局場景是相似的。本小節將介紹如何借助自適應布局、響應式布局以及常見的容器類組件,實現應用中的典型布局場景。
布局場景 | 實現方案 開發前請熟悉鴻蒙開發指導文檔:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md 點擊或者復制轉到。 |
---|---|
[頁簽欄] | Tab組件 + 響應式布局 |
[運營橫幅(Banner)] | Swiper組件 + 響應式布局 |
[網格] | Grid組件 / List組件 + 響應式布局 |
[側邊欄] | SideBar組件 + 響應式布局 |
[單/雙欄] | Navigation組件 + 響應式布局 |
[三分欄] | SideBar組件 + Navigation組件 + 響應式布局 |
[自定義彈窗] | CustomDialogController組件 + 響應式布局 |
[大圖瀏覽] | Image組件 |
[操作入口] | Scroll組件+Row組件橫向均分 |
[頂部] | 柵格組件 |
[縮進布局] | 柵格組件 |
[挪移布局] | 柵格組件 |
[重復布局] | 柵格組件 |
說明: 在本文[媒體查詢]小節中已經介紹了如何通過媒體查詢監聽斷點變化,后續的示例中不再重復介紹此部分代碼。
頁簽欄
布局效果
實現方案
不同斷點下,頁簽在頁面中的位置及尺寸都有差異,可以結合響應式布局能力,設置不同斷點下[Tab組件]的barPosition、vertical、barWidth和barHeight屬性實現目標效果。
另外,頁簽欄中的文字和圖片的相對位置不同,同樣可以通過設置不同斷點下[tabBar]對應的CustomBuilder中的布局方向,實現目標效果。
參考代碼
import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem'
interface TabBar {
name: string
icon: Resource
selectIcon: 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: string
image: 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: string
image: 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組件]實現,結合響應式布局能力,在不同斷點下為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
}
})
}
}
單/雙欄
布局效果
實現方案
單/雙欄場景可以使用[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?: Resource
private label?: string
build() {
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時呈現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.Window
private curBp?: string
private myWidth?: number
// ...
// 根據當前窗口尺寸更新斷點
private updateBreakpoint(windowWidth:number) :void{
// 將長度的單位由px換算為vp
let windowWidthVp = windowWidth / (display.getDefaultDisplaySync().densityDPI / 160)
let newBp: string = ''
let newWd: number
if (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 = newBp
this.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?: Resource
private label?: string
build() {
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)
}
}
自定義彈窗
布局效果
實現方案
自定義彈窗通常通過[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?: CustomDialogController
cancel?: () = > void
confirm?: () = > void
build() {
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?: CustomDialogController
cancel?: () = > void
confirm?: () = > void
build() {
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: string
icon: 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 = true
build() {
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 = 24
build() {
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 = 0
build() {
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事件,調整頁面中顯示的元素數量。
參考代碼
`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 = 24
build() {
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)
}
}
-
移動開發
+關注
關注
0文章
52瀏覽量
9780 -
鴻蒙系統
+關注
關注
183文章
2637瀏覽量
66507 -
HarmonyOS
+關注
關注
79文章
1980瀏覽量
30327 -
OpenHarmony
+關注
關注
25文章
3731瀏覽量
16425 -
鴻蒙OS
+關注
關注
0文章
189瀏覽量
4475
發布評論請先 登錄
相關推薦
評論