典型布局場(chǎng)景
雖然不同應(yīng)用的頁(yè)面千變?nèi)f化,但對(duì)其進(jìn)行拆分和分析,頁(yè)面中的很多布局場(chǎng)景是相似的。本小節(jié)將介紹如何借助自適應(yīng)布局、響應(yīng)式布局以及常見(jiàn)的容器類(lèi)組件,實(shí)現(xiàn)應(yīng)用中的典型布局場(chǎng)景。
布局場(chǎng)景 | 實(shí)現(xiàn)方案 開(kāi)發(fā)前請(qǐng)熟悉鴻蒙開(kāi)發(fā)指導(dǎo)文檔:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md 點(diǎn)擊或者復(fù)制轉(zhuǎn)到。 |
---|---|
[頁(yè)簽欄] | Tab組件 + 響應(yīng)式布局 |
[運(yùn)營(yíng)橫幅(Banner)] | Swiper組件 + 響應(yīng)式布局 |
[網(wǎng)格] | Grid組件 / List組件 + 響應(yīng)式布局 |
[側(cè)邊欄] | SideBar組件 + 響應(yīng)式布局 |
[單/雙欄] | Navigation組件 + 響應(yīng)式布局 |
[三分欄] | SideBar組件 + Navigation組件 + 響應(yīng)式布局 |
[自定義彈窗] | CustomDialogController組件 + 響應(yīng)式布局 |
[大圖瀏覽] | Image組件 |
[操作入口] | Scroll組件+Row組件橫向均分 |
[頂部] | 柵格組件 |
[縮進(jìn)布局] | 柵格組件 |
[挪移布局] | 柵格組件 |
[重復(fù)布局] | 柵格組件 |
說(shuō)明: 在本文[媒體查詢(xún)]小節(jié)中已經(jīng)介紹了如何通過(guò)媒體查詢(xún)監(jiān)聽(tīng)斷點(diǎn)變化,后續(xù)的示例中不再重復(fù)介紹此部分代碼。
頁(yè)簽欄
布局效果
實(shí)現(xiàn)方案
不同斷點(diǎn)下,頁(yè)簽在頁(yè)面中的位置及尺寸都有差異,可以結(jié)合響應(yīng)式布局能力,設(shè)置不同斷點(diǎn)下[Tab組件]的barPosition、vertical、barWidth和barHeight屬性實(shí)現(xiàn)目標(biāo)效果。
另外,頁(yè)簽欄中的文字和圖片的相對(duì)位置不同,同樣可以通過(guò)設(shè)置不同斷點(diǎn)下[tabBar]對(duì)應(yīng)的CustomBuilder中的布局方向,實(shí)現(xiàn)目標(biāo)效果。
參考代碼
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: '首頁(yè)',
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
})
}
}
運(yùn)營(yíng)橫幅(Banner)
布局效果
實(shí)現(xiàn)方案
運(yùn)營(yíng)橫幅通常使用[Swiper組件]實(shí)現(xiàn)。不同斷點(diǎn)下,運(yùn)營(yíng)橫幅中展示的圖片數(shù)量不同。只需要結(jié)合響應(yīng)式布局,配置不同斷點(diǎn)下Swiper組件的displayCount屬性,即可實(shí)現(xiàn)目標(biāo)效果。
參考代碼
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)!)
}
}
網(wǎng)格
布局效果
實(shí)現(xiàn)方案
不同斷點(diǎn)下,頁(yè)面中圖片的排布不同,此場(chǎng)景可以通過(guò)響應(yīng)式布局能力結(jié)合[Grid組件]實(shí)現(xiàn),通過(guò)調(diào)整不同斷點(diǎn)下的Grid組件的columnsTemplate屬性即可實(shí)現(xiàn)目標(biāo)效果。
另外,由于本例中各列的寬度相同,也可以通過(guò)響應(yīng)式布局能力結(jié)合[List組件]實(shí)現(xiàn),通過(guò)調(diào)整不同斷點(diǎn)下的List組件的lanes屬性也可實(shí)現(xiàn)目標(biāo)效果。
參考代碼
通過(guò)Grid組件實(shí)現(xiàn)
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)!)
}
}
通過(guò)List組件實(shí)現(xiàn)
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%')
}
}
側(cè)邊欄
布局效果
實(shí)現(xiàn)方案
側(cè)邊欄通常通過(guò)[SideBarContainer組件]實(shí)現(xiàn),結(jié)合響應(yīng)式布局能力,在不同斷點(diǎn)下為SideBarConContainer組件的sideBarWidth、showControlButton等屬性配置不同的值,即可實(shí)現(xiàn)目標(biāo)效果。
參考代碼
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
}
})
}
}
單/雙欄
布局效果
實(shí)現(xiàn)方案
單/雙欄場(chǎng)景可以使用[Navigation組件]實(shí)現(xiàn),Navigation組件可以根據(jù)窗口寬度自動(dòng)切換單/雙欄顯示,減少開(kāi)發(fā)工作量。
參考代碼
@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')
}
}
三分欄
布局效果
場(chǎng)景說(shuō)明
為充分利用設(shè)備的屏幕尺寸優(yōu)勢(shì),應(yīng)用在大屏設(shè)備上常常有二分欄或三分欄的設(shè)計(jì),即“A+C”,“B+C”或“A+B+C”的組合,其中A是側(cè)邊導(dǎo)航區(qū),B是列表導(dǎo)航區(qū),C是內(nèi)容區(qū)。在用戶(hù)動(dòng)態(tài)改變窗口寬度時(shí),當(dāng)窗口寬度大于或等于840vp時(shí)頁(yè)面呈現(xiàn)A+B+C三列,放大縮小優(yōu)先變化C列;當(dāng)窗口寬度小于840vp大于等于600vp時(shí)呈現(xiàn)A+C列,放大縮小時(shí)優(yōu)先變化C列;當(dāng)窗口寬度小于600vp大于等于360vp時(shí),僅呈現(xiàn)C列。
實(shí)現(xiàn)方案
三分欄場(chǎng)景可以組合使用[SideBarContainer]組件與[Navigation組件]實(shí)現(xiàn),SideBarContainer組件可以通過(guò)側(cè)邊欄控制按鈕控制顯示/隱藏,Navigation組件可以根據(jù)窗口寬度自動(dòng)切換該組件內(nèi)單/雙欄顯示,結(jié)合響應(yīng)式布局能力,在不同斷點(diǎn)下為SideBarConContainer組件的minContentWidth屬性配置不同的值,即可實(shí)現(xiàn)目標(biāo)效果。設(shè)置minContentWidth屬性的值可以通過(guò)[斷點(diǎn)]監(jiān)聽(tīng)窗口尺寸變化的同時(shí)設(shè)置不同的值并儲(chǔ)存成一個(gè)全局對(duì)象。
參考代碼
// 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
// ...
// 根據(jù)當(dāng)前窗口尺寸更新斷點(diǎn)
private updateBreakpoint(windowWidth:number) :void{
// 將長(zhǎng)度的單位由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
// 使用狀態(tài)變量記錄當(dāng)前斷點(diǎn)值
AppStorage.setOrCreate('currentBreakpoint', this.curBp)
// 使用狀態(tài)變量記錄當(dāng)前minContentWidth值
AppStorage.setOrCreate('myWidth', this.myWidth)
}
}
onWindowStageCreate(windowStage: window.WindowStage) :void{
windowStage.getMainWindow().then((windowObj) = > {
this.windowObj = windowObj
// 獲取應(yīng)用啟動(dòng)時(shí)的窗口尺寸
this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width)
// 注冊(cè)回調(diào)函數(shù),監(jiān)聽(tīng)窗口尺寸變化
windowObj.on('windowSizeChange', (windowSize)= >{
this.updateBreakpoint(windowSize.width)
})
});
// ...
}
// 窗口銷(xiāo)毀時(shí),取消窗口尺寸變化監(jiān)聽(tīng)
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)
}
}
自定義彈窗
布局效果
實(shí)現(xiàn)方案
自定義彈窗通常通過(guò)[CustomDialogController]實(shí)現(xiàn),有兩種方式實(shí)現(xiàn)本場(chǎng)景的目標(biāo)效果:
- 通過(guò)gridCount屬性配置自定義彈窗的寬度。
系統(tǒng)默認(rèn)對(duì)不同斷點(diǎn)下的窗口進(jìn)行了柵格化:sm斷點(diǎn)下為4柵格,md斷點(diǎn)下為8柵格,lg斷點(diǎn)下為12柵格。通過(guò)gridCount屬性可以配置彈窗占據(jù)柵格中的多少列,將該值配置為4即可實(shí)現(xiàn)目標(biāo)效果。 - 將customStyle設(shè)置為true,即彈窗的樣式完全由開(kāi)發(fā)者自定義。
開(kāi)發(fā)者自定義彈窗樣式時(shí),開(kāi)發(fā)者可以根據(jù)需要配置彈窗的寬高和背景色(非彈窗區(qū)域保持默認(rèn)的半透明色)。自定義彈窗樣式配合[柵格組件]同樣可以實(shí)現(xiàn)目標(biāo)效果。
參考代碼
@Entry
@Component
struct CustomDialogSample {
// 通過(guò)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('是否刪除此聯(lián)系人?')
.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('是否刪除此聯(lián)系人?')
.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})
}
}
大圖瀏覽
布局效果
實(shí)現(xiàn)方案
圖片通常使用[Image組件]展示,Image組件的objectFit屬性默認(rèn)為ImageFit.Cover,即保持寬高比進(jìn)行縮小或者放大以使得圖片兩邊都大于或等于顯示邊界。在大圖瀏覽場(chǎng)景下,因屏幕與圖片的寬高比可能有差異,常常會(huì)發(fā)生圖片被截?cái)嗟膯?wèn)題。此時(shí)只需將Image組件的objectFit屬性設(shè)置為ImageFit.Contain,即保持寬高比進(jìn)行縮小或者放大并使得圖片完全顯示在顯示邊界內(nèi),即可解決該問(wèn)題。
參考代碼
@Entry
@Component
struct BigImage {
build() {
Row() {
Image($r("app.media.image"))
.objectFit(ImageFit.Contain)
}
}
}
操作入口
布局效果
實(shí)現(xiàn)方案
Scroll(內(nèi)容超出寬度時(shí)可滾動(dòng)) + 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: '熱門(mén)', icon: $r('app.media.hot') },
{ name: '運(yùn)動(dòng)音樂(lè)', icon: $r('app.media.sport') },
{ name: '音樂(lè)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)
}
}
頂部
布局效果
實(shí)現(xiàn)方案
最外層使用柵格行組件GridRow布局
文本標(biāo)題使用柵格列組件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: '猜您喜歡: 萬(wàn)水千山' })
.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 })
}
}
縮進(jìn)布局
布局效果
實(shí)現(xiàn)方案
借助[柵格組件],控制待顯示內(nèi)容在不同的斷點(diǎn)下占據(jù)不同的列數(shù),即可實(shí)現(xiàn)不同設(shè)備上的縮進(jìn)效果。另外還可以調(diào)整不同斷點(diǎn)下柵格組件與兩側(cè)的間距,獲得更好的顯示效果。
參考代碼
@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 })
}
}
挪移布局
布局效果
實(shí)現(xiàn)方案
不同斷點(diǎn)下,柵格子元素占據(jù)的列數(shù)會(huì)隨著開(kāi)發(fā)者的配置發(fā)生改變。當(dāng)一行中的列數(shù)超過(guò)柵格組件在該斷點(diǎn)的總列數(shù)時(shí),可以自動(dòng)換行,即實(shí)現(xiàn)”上下布局”與”左右布局”之間切換的效果。
參考代碼
@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')
}
}
重復(fù)布局
布局效果
實(shí)現(xiàn)方案
不同斷點(diǎn)下,配置柵格子組件占據(jù)不同的列數(shù),即可實(shí)現(xiàn)“小屏單列顯示、大屏雙列顯示”的效果。另外,還可以通過(guò)柵格組件的onBreakpointChange事件,調(diào)整頁(yè)面中顯示的元素?cái)?shù)量。
參考代碼
`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() {
// 當(dāng)目標(biāo)區(qū)域不足以顯示所有元素時(shí),可以通過(guò)上下滑動(dòng)查看不同的元素
Scroll() {
GridRow({gutter: 24}) {
ForEach(this.listItems, () = > {
// 通過(guò)配置元素在不同斷點(diǎn)下占的列數(shù),實(shí)現(xiàn)不同的布局效果
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)
}
}
-
移動(dòng)開(kāi)發(fā)
+關(guān)注
關(guān)注
0文章
52瀏覽量
9734 -
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2634瀏覽量
66302 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1973瀏覽量
30143 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3713瀏覽量
16254 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
188瀏覽量
4382
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論