鴻蒙一多適配的布局能力

不同的屏幕尺寸,如何適配?鴻蒙提供了自適應(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種能力可以用在什么地方。


使用自適應(yīng)布局

上圖是一個(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)查看官方文檔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容