不同的屏幕尺寸,如何適配?鴻蒙提供了自適應(yīng)布局和響應(yīng)式布局。
| 名稱 | 簡(jiǎn)介 |
|---|---|
| 自適應(yīng)布局 | 拉伸屏幕,頁(yè)面的位置關(guān)系沒(méi)有發(fā)生變化。自適應(yīng)布局常常需要借助Row組件、Column組件或Flex組件實(shí)現(xiàn)。當(dāng)前自適應(yīng)布局能力有7種:拉伸能力、均分能力、占比能力、縮放能力、延伸能力、隱藏能力、折行能力。 |
| 響應(yīng)式布局 | 拉伸屏幕,頁(yè)面的位置關(guān)系發(fā)生變化。響應(yīng)式布局常常與GridRow組件、List組件、Swiper組件或Tabs組件搭配使用。響應(yīng)式布局能力有3種:斷點(diǎn)、媒體查詢、柵格布局。 |
自適應(yīng)布局
下面介紹自適應(yīng)布局的7種能力。自適應(yīng)布局的7種能力需要牢記于心。
拉伸能力
父組件尺寸發(fā)生變化,增加或減小指定組件的尺寸。
| 屬性 | 默認(rèn)值 | 描述 |
|---|---|---|
| flexGrow | 0 | 父容器寬度大于所有子組件寬度的總和,子組件按照比例分配父容器的多余空間。 |
| flexShrink | 1 | 父容器寬度小于所有子組件寬度的總和。子組件按照比例收縮分配父容器的不足空間。 |
| flexBasis | 'auto' | 設(shè)置組件在Flex容器中主軸方向上基準(zhǔn)尺寸。'auto'意味著使用組件原始的尺寸,不做修改。flexBasis屬性不是必須的,通過(guò)width或height也可以達(dá)到同樣的效果。當(dāng)flexBasis屬性與width或height發(fā)生沖突時(shí),以flexBasis屬性為準(zhǔn)。 |
下面的示例中,頁(yè)面由中間的圖片以及兩側(cè)的留白區(qū)組成,各區(qū)域的屬性配置如下:
- 中間內(nèi)容區(qū)的寬度設(shè)置為400vp,同時(shí)將flexGrow屬性設(shè)置為1,flexShrink屬性設(shè)置為0。
- 兩側(cè)留白區(qū)的寬度設(shè)置為150vp,同時(shí)將flexGrow屬性設(shè)置為0,flexShrink屬性設(shè)置為1。
父容器的基準(zhǔn)尺寸是700vp(150vp+400vp+150vp)??梢酝ㄟ^(guò)拖動(dòng)底部的滑動(dòng)條改變父容器的尺寸,查看布局變化。 - 當(dāng)父容器的尺寸大于700vp時(shí),父容器中多余的空間全部分配給中間內(nèi)容區(qū)。
-
當(dāng)父容器的尺寸小于700vp時(shí),左右兩側(cè)的留白區(qū)按照“1:1”的比例收縮。
示例圖
@Entry
@Component
struct FlexibleCapabilitySample1 {
@State containerWidth: number = 402
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽纭? @Builder slider() {
Slider({ value: this.containerWidth, min: 402, max: 1000, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.containerWidth = value;
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Column() {
Row() {
// 通過(guò)flexGrow和flexShrink屬性,將多余的空間全部分配給圖片,將不足的控件全部分配給兩側(cè)空白區(qū)域。
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
Image($r("app.media.illustrator")).width(400).height(400)
.objectFit(ImageFit.Contain)
.backgroundColor("#66F1CCB8")
.flexGrow(1).flexShrink(0)
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
}
.width(this.containerWidth)
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
}
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
如果期望將父容器的剩余空間全部分配給某空白區(qū)域時(shí),也可以通過(guò)Blank組件實(shí)現(xiàn)。注意僅當(dāng)父組件為Row、Column、Flex組件時(shí),Blank組件才會(huì)生效。

@Entry
@Component
struct FlexibleCapabilitySample2 {
@State rate: number = 0.8
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 80, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Column() {
Row() {
Text('飛行模式')
.fontSize(16)
.width(135)
.height(22)
.fontWeight(FontWeight.Medium)
.lineHeight(22)
Blank() // 通過(guò)Blank組件實(shí)現(xiàn)拉伸能力
Toggle({ type: ToggleType.Switch })
.width(36)
.height(20)
}
.height(55)
.borderRadius(12)
.padding({ left: 13, right: 13 })
.backgroundColor('#FFFFFF')
.width(this.rate * 100 + '%')
}
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
均分能力
父容器縮放,子組件的尺寸不變,只是中間的間距或者留白按照比例縮放。均分能力可以通過(guò)將Row組件、Column組件或Flex組件的justifyContent屬性設(shè)置為FlexAlign.SpaceEvenly實(shí)現(xiàn)。
占比能力
子組件的寬高按照預(yù)設(shè)的比例,隨父容器組件發(fā)生變化。占比能力通常有兩種實(shí)現(xiàn)方式:
- 將子組件的寬高設(shè)置為父組件寬高的百分比。
-
設(shè)置權(quán)重layoutWeight屬性。
示例圖
@Entry
@Component
struct ProportionCapabilitySample {
@State rate: number = 0.5
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: 100, min: 25, max: 50, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Column() {
Row() {
Column() {
Image($r("app.media.down"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 設(shè)置子組件在父容器主軸方向的布局權(quán)重
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
Column() {
Image($r("app.media.pause"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 設(shè)置子組件在父容器主軸方向的布局權(quán)重
.backgroundColor('#66F1CCB8')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
Column() {
Image($r("app.media.next"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 設(shè)置子組件在父容器主軸方向的布局權(quán)重
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
}
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
縮放能力
設(shè)置aspectRatio寬高比屬性,父容器發(fā)生改變,子組件的寬高比不變。

@Entry
@Component
struct ScaleCapabilitySample {
@State sliderWidth: number = 400
@State sliderHeight: number = 400
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: this.sliderHeight, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderHeight = value
})
.position({ x: '20%', y: '80%' })
Slider({ value: this.sliderWidth, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderWidth = value;
})
.position({ x: '20%', y: '87%' })
}
build() {
Column() {
Column() {
Column() {
Image($r("app.media.illustrator")).width('100%').height('100%')
}
.aspectRatio(1) // 固定寬高比
.border({ width: 2, color: "#66F1CCB8"}) // 邊框,僅用于展示效果
}
.backgroundColor("#FFFFFF")
.height(this.sliderHeight)
.width(this.sliderWidth)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor("#F1F3F5")
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
延伸能力
子組件隨著父容器尺寸變化顯示或者隱藏。延伸能力通常有兩種實(shí)現(xiàn)方式:
- 通過(guò)List組件。
-
通過(guò)Scroll組件配合Row組件或Column組件實(shí)現(xiàn)。
示例圖
@Entry
@Component
struct ExtensionCapabilitySample1 {
@State rate: number = 0.60
readonly appList: number [] = [0, 1, 2, 3, 4, 5, 6, 7]
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row({ space: 10 }) {
// 通過(guò)List組件實(shí)現(xiàn)隱藏能力
List({ space: 10 }) {
ForEach(this.appList, (item:number) => {
ListItem() {
Column() {
Image($r("app.media.startIcon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
}.width(80).height(102)
})
}
.padding({ top: 16, left: 10 })
.listDirection(Axis.Horizontal)
.width('100%')
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
隱藏能力
給子組件設(shè)置布局優(yōu)先級(jí)(displayPriority屬性),父組件尺寸變化,按照優(yōu)先級(jí)對(duì)子組件進(jìn)行顯示或者隱藏。

@Entry
@Component
struct HiddenCapabilitySample {
@State rate: number = 0.45
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: this.rate * 100, min: 10, max: 45, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Column() {
Row() {
Image($r("app.media.favorite"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局優(yōu)先級(jí)
Image($r("app.media.down"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局優(yōu)先級(jí)
Image($r("app.media.pause"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(3) // 布局優(yōu)先級(jí)
Image($r("app.media.next"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局優(yōu)先級(jí)
Image($r("app.media.list"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局優(yōu)先級(jí)
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
折行能力
折行能力通過(guò)使用 Flex折行布局 (將wrap屬性設(shè)置為FlexWrap.Wrap)實(shí)現(xiàn),當(dāng)橫向布局尺寸不足以完整顯示內(nèi)容元素時(shí),通過(guò)折行的方式,將元素顯示在下方。

@Entry
@Component
struct WrapCapabilitySample {
@State rate: number = 0.7
readonly imageList: Resource [] = [
$r('app.media.flexWrap1'),
$r('app.media.flexWrap2'),
$r('app.media.flexWrap3'),
$r('app.media.flexWrap4'),
$r('app.media.flexWrap5'),
$r('app.media.flexWrap6')
]
// 底部滑塊,可以通過(guò)拖拽滑塊改變?nèi)萜鞒叽? @Builder slider() {
Slider({ value: this.rate * 100, min: 50, max: 70, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
Column() {
// 通過(guò)Flex組件warp參數(shù)實(shí)現(xiàn)自適應(yīng)折行
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center,
wrap: FlexWrap.Wrap
}) {
ForEach(this.imageList, (item:Resource) => {
Image(item).width(183).height(138).padding(10)
})
}
.backgroundColor('#FFFFFF')
.padding(20)
.width(this.rate * 100 + '%')
.borderRadius(16)
}
.width('100%')
this.slider()
}.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
這就是自適應(yīng)布局的7種能力,下面給出一個(gè)案例,主要是想讓大家知道這7種能力可以用在什么地方。

上圖是一個(gè)音樂(lè)播放器,左邊是音樂(lè)播放器在平板上的顯示效果,中間是音樂(lè)播放器在手機(jī)上的顯示效果,右邊是音樂(lè)播放器在折疊屏上的顯示效果。我們把音樂(lè)播放器分為6個(gè)區(qū)域。
| 區(qū)域 | 布局能力 | 實(shí)現(xiàn)方案 |
|---|---|---|
| 1、標(biāo)題欄 | 自適應(yīng)布局-拉伸能力 | 外層使用Row組件,內(nèi)層的留白組件自帶拉伸能力。 |
| 2、專輯圖片 | 自適應(yīng)布局-縮放能力 | 設(shè)置圖片aspectRatio屬性,將寬高比設(shè)置1:1。 |
| 3、收藏/下載/評(píng)論/分享 | 自適應(yīng)布局-均分能力 | justifyContent屬性設(shè)置為FlexAlign.SpaceEvenly。 |
| 4、底部播放量 | 自適應(yīng)布局-占比能力 | 設(shè)置layoutWeight屬性,將左側(cè)與右側(cè)占比為3:1。 |
| 5、收藏/播放/上一首/下一首 | 自適應(yīng)布局-隱藏能力 | 設(shè)置優(yōu)先級(jí)displayPriority屬性,平板顯示5個(gè)按鈕,折疊屏顯示3個(gè)按鈕,手機(jī)顯示一個(gè)按鈕。 |
| 6、音樂(lè)列表 | 自適應(yīng)布局-延伸能力 | 設(shè)置lanes,列表顯示1列或者兩列。 |
響應(yīng)式布局
拉伸屏幕,頁(yè)面的位置關(guān)系發(fā)生變化。自適應(yīng)布局可以保證窗口尺寸在一定范圍內(nèi)變化時(shí),頁(yè)面的顯示是正常的。但是將窗口尺寸變化較大時(shí)(如窗口寬度從400vp變化為1000vp),僅僅依靠自適應(yīng)布局可能出現(xiàn)圖片異常放大或頁(yè)面內(nèi)容稀疏、留白過(guò)多等問(wèn)題,此時(shí)就需要借助響應(yīng)式布局能力調(diào)整頁(yè)面結(jié)構(gòu)。
斷點(diǎn)
將窗口寬度劃分為不同的范圍(即斷點(diǎn)),監(jiān)聽(tīng)窗口尺寸變化,當(dāng)斷點(diǎn)改變時(shí)同步調(diào)整頁(yè)面布局。斷點(diǎn)支持自定義,取值范圍可以修改,下標(biāo)是4個(gè)常見(jiàn)斷點(diǎn)范圍。
| 名稱 | 取值范圍 |
|---|---|
| xs(超小,智能穿戴類設(shè)備) | [0, 320) |
| sm(小,手機(jī)) | [320, 600) |
| xs(中等,折疊屏) | [600, 840) |
| xs(大,平板) | [840, +∞) |
可以根據(jù)實(shí)際需要在lg斷點(diǎn)后面新增xl、xxl等斷點(diǎn),但注意新增斷點(diǎn)會(huì)同時(shí)增加設(shè)計(jì)師及開(kāi)發(fā)者的工作量。
系統(tǒng)提供了多種方法,判斷應(yīng)用當(dāng)前處于何種斷點(diǎn),進(jìn)而可以調(diào)整應(yīng)用的布局。先介紹如何通過(guò)窗口對(duì)象監(jiān)聽(tīng)斷點(diǎn)變化。
在UIAbility的onWindowStageCreate生命周期回調(diào)中,通過(guò)窗口對(duì)象獲取啟動(dòng)時(shí)的應(yīng)用窗口寬度并注冊(cè)回調(diào)函數(shù)監(jiān)聽(tīng)窗口尺寸變化。將窗口尺寸的長(zhǎng)度單位由px換算為vp后,即可基于前文中介紹的規(guī)則得到當(dāng)前斷點(diǎn)值,此時(shí)可以使用狀態(tài)變量記錄當(dāng)前的斷點(diǎn)值方便后續(xù)使用。
// MainAbility.ts
import window from '@ohos.window'
import display from '@ohos.display'
import UIAbility from '@ohos.app.ability.UIAbility'
export default class MainAbility extends UIAbility {
private windowObj?: window.Window
private curBp: string = ''
//...
// 根據(jù)當(dāng)前窗口尺寸更新斷點(diǎn)
private updateBreakpoint(windowWidth: number) :void{
// 將長(zhǎng)度的單位由px換算為vp
let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels
let newBp: string = ''
if (windowWidthVp < 320) {
newBp = 'xs'
} else if (windowWidthVp < 600) {
newBp = 'sm'
} else if (windowWidthVp < 840) {
newBp = 'md'
} else {
newBp = 'lg'
}
if (this.curBp !== newBp) {
this.curBp = newBp
// 使用狀態(tài)變量記錄當(dāng)前斷點(diǎn)值
AppStorage.setOrCreate('currentBreakpoint', this.curBp)
}
}
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)
})
});
// ...
}
//...
}
在頁(yè)面中,獲取及使用當(dāng)前的斷點(diǎn)。
@Entry
@Component
struct Index {
@StorageProp('currentBreakpoint') curBp: string = 'sm'
build() {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text(this.curBp).fontSize(50).fontWeight(FontWeight.Medium)
}
.width('100%')
.height('100%')
}
}
媒體查詢
媒體查詢提供了豐富的媒體特征監(jiān)聽(tīng)能力,可以監(jiān)聽(tīng)?wèi)?yīng)用顯示區(qū)域變化、橫豎屏、深淺色、設(shè)備類型等等,因此在應(yīng)用開(kāi)發(fā)過(guò)程中使用的非常廣泛。下面通過(guò)通過(guò)媒體查詢,監(jiān)聽(tīng)?wèi)?yīng)用窗口寬度變化,獲取當(dāng)前應(yīng)用所處的斷點(diǎn)值。
export class BreakpointSystem {
private currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;
// 監(jiān)聽(tīng)sm的屏幕尺寸
private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_SM);
// 監(jiān)聽(tīng)md的屏幕尺寸
private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_MD);
// 監(jiān)聽(tīng)lg的屏幕尺寸
private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_LG);
private updateCurrentBreakpoint(breakpoint: string): void {
if (this.currentBreakpoint !== breakpoint) {
this.currentBreakpoint = breakpoint;
// 將斷點(diǎn)保存到AppStorage
AppStorage.setOrCreate<string>(BreakpointConstants.CURRENT_BREAKPOINT, this.currentBreakpoint);
}
}
private isBreakpointSM = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM);
}
}
private isBreakpointMD = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD);
}
}
private isBreakpointLG = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG);
}
}
public register(): void {
this.smListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_SM);
this.smListener.on('change', this.isBreakpointSM);
this.mdListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_MD);
this.mdListener.on('change', this.isBreakpointMD);
this.lgListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_LG);
this.lgListener.on('change', this.isBreakpointLG);
}
public unregister(): void {
this.smListener.off('change', this.isBreakpointSM);
this.mdListener.off('change', this.isBreakpointMD);
this.lgListener.off('change', this.isBreakpointLG);
}
}
在上述代碼中,我們定義不同的屏幕尺寸監(jiān)聽(tīng),通過(guò)媒體查詢mediaquery.matchMediaSync來(lái)監(jiān)聽(tīng)屏幕尺寸。將監(jiān)聽(tīng)到的屏幕尺寸保存AppStorage,這樣其它頁(yè)面就能通過(guò)AppStorage獲取屏幕尺寸。同時(shí)提供注冊(cè)register方法和注銷unregister方法。
@Entry
@Component
struct MediaQuerySample {
@StorageLink('currentBreakpoint') private currentBreakpoint: string = "md";
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text(this.currentBreakpoint)
.fontSize(24)
.margin(10)
}
.width('100%')
.height('100%')
}
}
在上述代碼中,在aboutToAppear中注冊(cè)媒體查詢,在aboutToDisappear中注銷媒體查詢。由于斷點(diǎn)保存在AppStorage,所以可以直接使用@StorageLink裝飾器從AppStorage中取出斷點(diǎn)。
柵格布局
根據(jù)設(shè)備的寬度,將不同的屏幕尺寸劃分為不同數(shù)量的柵格,來(lái)實(shí)現(xiàn)屏幕的自適應(yīng)。如下圖,小尺寸的手機(jī)可以畫(huà)4個(gè)柵格,折疊屏可以畫(huà)8個(gè)柵格,平板可以畫(huà)12個(gè)柵格。一般來(lái)說(shuō),推薦按照4、8、12的比例進(jìn)行柵格劃分。柵格和柵格之前有12vp的間距,如果沒(méi)有間距,柵格就會(huì)擠在一起。

span用于設(shè)置柵格的數(shù)量,offset用于設(shè)置偏移量。如下圖,手機(jī)設(shè)置4個(gè)柵格,不設(shè)置偏移量。折疊屏總共有8個(gè)柵格,設(shè)置6個(gè)柵格,偏移1個(gè)柵格,就達(dá)到了居中的效果。平板總共有12個(gè)柵格,設(shè)置8個(gè)柵格,偏移2個(gè)柵格,就達(dá)到了居中的效果。

下面的代碼就實(shí)現(xiàn)了上面所說(shuō)的在不同設(shè)備上的登錄頁(yè)面。
build() {
GridRow({
/**
* columns用于指定不同設(shè)備占據(jù)的總柵格數(shù),默認(rèn)情況下,總柵格數(shù)為12
* 指定手機(jī)的總柵格數(shù)為4,折疊屏總柵格數(shù)為8,平板總柵格數(shù)為12。
*/
columns:{sm: 4, md: 8, lg: 12},
// 間距
gutter: 12
}) {
// 子組件
GridCol({
// 手機(jī)占4個(gè)柵格,折疊屏占8個(gè)柵,平板占12個(gè)柵格。
span: {sm: 4, md: 6, lg: 8},
// 手機(jī)不偏移,折疊屏偏移一個(gè)柵格,平板偏移2個(gè)柵格。
offset: {sm: 0, md: 1, lg: 2}
}) {
// 登錄頁(yè)面
this.loginUI()
}
}
}
柵格組件提供了豐富的自定義能力,功能異常靈活和強(qiáng)大。只需要明確柵格在不同斷點(diǎn)下的Columns、Margin、Gutter及span等參數(shù),即可確定最終布局,無(wú)需關(guān)心具體的設(shè)備類型及設(shè)備狀態(tài)(如橫豎屏)等。以上只是簡(jiǎn)單的介紹了下柵格布局,估計(jì)有人沒(méi)看懂,關(guān)于柵格布局的詳細(xì)文檔還請(qǐng)查看官方文檔。


