1# 响应式布局
2
3
4自适应布局可以保证窗口尺寸在一定范围内变化时,页面的显示是正常的。但是将窗口尺寸变化较大时(如窗口宽度从400vp变化为1000vp),仅仅依靠自适应布局可能出现图片异常放大或页面内容稀疏、留白过多等问题,此时就需要借助响应式布局能力调整页面结构。
5
6
7响应式布局是指页面内的元素可以根据特定的特征(如窗口宽度、屏幕方向等)自动变化以适应外部容器变化的布局能力。响应式布局中最常使用的特征是窗口宽度,可以将窗口宽度划分为不同的范围(下文中称为断点)。当窗口宽度从一个断点变化到另一个断点时,改变页面布局(如将页面内容从单列排布调整为双列排布甚至三列排布等)以获得更好的显示效果。
8
9
10当前系统提供了如下三种响应式布局能力,后文中我们将依次展开介绍。
11
12
13  | 响应式布局能力 | 简介 |
14| -------- | -------- |
15| [断点](#断点) | 将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。 |
16| [媒体查询](#媒体查询) | 媒体查询支持监听窗口宽度、横竖屏、深浅色、设备类型等多种媒体特征,当媒体特征发生改变时同步调整页面布局。 |
17| [栅格布局](#栅格布局) | 栅格组件将其所在的区域划分为有规律的多列,通过调整不同断点下的栅格组件的参数以及其子组件占据的列数等,实现不同的布局效果。 |
18
19
20## 断点
21
22断点以应用窗口宽度为切入点,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,在不同的区间下,开发者可根据需要实现不同的页面布局效果。具体的断点如下所示。
23
24  | 断点名称 | 取值范围(vp) |
25| -------- | -------- |
26| xs | [0, 320) |
27| sm | [320, 600) |
28| md | [600, 840) |
29| lg | [840, +∞) |
30
31> **说明:**
32>
33> - 以设备屏幕宽度作为参照物,也可以实现类似的效果。考虑到应用可能以非全屏窗口的形式显示,以应用窗口宽度为参照物更为通用。
34>
35> - 开发者可以根据实际使用场景决定适配哪些断点。如xs断点对应的一般是智能穿戴类设备,如果确定某页面不会在智能穿戴设备上显示,则可以不适配xs断点。
36>
37> - 可以根据实际需要在lg断点后面新增xl、xxl等断点,但注意新增断点会同时增加UX设计师及应用开发者的工作量,除非必要否则不建议盲目新增断点。
38
39系统提供了多种方法,判断应用当前处于何种断点,进而可以调整应用的布局。常见的监听断点变化的方法如下所示:
40
41- 获取窗口对象并监听窗口尺寸变化
42
43- 通过媒体查询监听应用窗口尺寸变化
44
45- 借助栅格组件能力监听不同断点的变化
46
47本小节中,先介绍如何通过窗口对象监听断点变化,后续的媒体查询及栅格章节中,将进一步展开介绍另外两种方法。
48
49通过窗口对象监听断点变化的核心是获取窗口对象及注册窗口尺寸变化的回调函数。
50
511. 在UIAbility的[onWindowStageCreate](../../application-models/uiability-lifecycle.md)生命周期回调中,通过[窗口](../../reference/apis-arkui/js-apis-window.md)对象获取启动时的应用窗口宽度并注册回调函数监听窗口尺寸变化。将窗口尺寸的长度单位[由px换算为vp](../../reference/apis-arkui/arkui-ts/ts-pixel-units.md)后,即可基于前文中介绍的规则得到当前断点值,此时可以使用[状态变量](../../quick-start/arkts-state.md)记录当前的断点值方便后续使用。
52
53   ```ts
54   // MainAbility.ts
55   import { window, display } from '@kit.ArkUI'
56   import { UIAbility } from '@kit.AbilityKit'
57
58   export default class MainAbility extends UIAbility {
59     private windowObj?: window.Window
60     private curBp: string = ''
61     //...
62     // 根据当前窗口尺寸更新断点
63     private updateBreakpoint(windowWidth: number) :void{
64       // 将长度的单位由px换算为vp
65       let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels
66       let newBp: string = ''
67       if (windowWidthVp < 320) {
68         newBp = 'xs'
69       } else if (windowWidthVp < 600) {
70         newBp = 'sm'
71       } else if (windowWidthVp < 840) {
72         newBp = 'md'
73       } else {
74         newBp = 'lg'
75       }
76       if (this.curBp !== newBp) {
77         this.curBp = newBp
78         // 使用状态变量记录当前断点值
79         AppStorage.setOrCreate('currentBreakpoint', this.curBp)
80       }
81     }
82
83     onWindowStageCreate(windowStage: window.WindowStage) :void{
84       windowStage.getMainWindow().then((windowObj) => {
85         this.windowObj = windowObj
86         // 获取应用启动时的窗口尺寸
87         this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width)
88         // 注册回调函数,监听窗口尺寸变化
89         windowObj.on('windowSizeChange', (windowSize)=>{
90           this.updateBreakpoint(windowSize.width)
91         })
92       });
93      // ...
94     }
95
96     //...
97   }
98   ```
99
1002. 在页面中,获取及使用当前的断点。
101
102   ```ts
103   @Entry
104   @Component
105   struct Index {
106     @StorageProp('currentBreakpoint') curBp: string = 'sm'
107
108     build() {
109       Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
110         Text(this.curBp).fontSize(50).fontWeight(FontWeight.Medium)
111       }
112       .width('100%')
113       .height('100%')
114     }
115   }
116   ```
117
1183. 运行及验证效果。
119   | | | |
120   | -------- | -------- | -------- |
121   | ![zh-cn_image_0000001336485520](figures/zh-cn_image_0000001336485520.jpg) | ![zh-cn_image_0000001386645965](figures/zh-cn_image_0000001386645965.jpg) | ![zh-cn_image_0000001386325621](figures/zh-cn_image_0000001386325621.jpg) |
122
123
124## 媒体查询
125
126
127在实际应用开发过程中,开发者常常需要针对不同类型设备或同一类型设备的不同状态来修改应用的样式。媒体查询提供了丰富的媒体特征监听能力,可以监听应用显示区域变化、横竖屏、深浅色、设备类型等等,因此在应用开发过程中使用的非常广泛。
128
129
130本小节仅介绍**媒体查询跟断点的结合**,即如何借助媒体查询能力,监听[断点](#断点)的变化,读者可以自行查阅官网中关于[媒体查询](../../ui/arkts-layout-development-media-query.md)的相关介绍了解更详细的用法。
131
132
133> **说明:**
134> 类Web开发范式,支持在js文件和css文件中使用媒体查询,请查看[js媒体查询](../../reference/apis-arkui/js-apis-mediaquery.md)和[css媒体查询](../../reference/apis-arkui/arkui-js/js-components-common-mediaquery.md)了解详细用法。
135
136
137**示例:**
138
139
140通过媒体查询,监听应用窗口宽度变化,获取当前应用所处的断点值。
141
142
143  | | | |
144| -------- | -------- | -------- |
145| ![zh-cn_image_0000001336165712](figures/zh-cn_image_0000001336165712.jpg) | ![zh-cn_image_0000001386485617](figures/zh-cn_image_0000001386485617.jpg) | ![zh-cn_image_0000001386805569](figures/zh-cn_image_0000001386805569.jpg) |
146
147
1481.对通过媒体查询监听[断点](#断点)的功能做简单的封装,方便后续使用
149```ts
150// common/breakpointsystem.ets
151import { mediaquery } from '@kit.ArkUI'
152
153export type BreakpointType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
154
155export interface Breakpoint {
156  name: BreakpointType
157  size: number
158  mediaQueryListener?: mediaquery.MediaQueryListener
159}
160
161export class BreakpointSystem {
162  private static instance: BreakpointSystem
163  private readonly breakpoints: Breakpoint[] = [
164    { name: 'xs', size: 0 },
165    { name: 'sm', size: 320 },
166    { name: 'md', size: 600 },
167    { name: 'lg', size: 840 }
168  ]
169  private states: Set<BreakpointState<Object>>
170
171  private constructor() {
172    this.states = new Set()
173  }
174
175  public static getInstance(): BreakpointSystem {
176    if (!BreakpointSystem.instance) {
177      BreakpointSystem.instance = new BreakpointSystem();
178    }
179    return BreakpointSystem.instance
180  }
181
182  public attach(state: BreakpointState<Object>): void {
183    this.states.add(state)
184  }
185
186  public detach(state: BreakpointState<Object>): void {
187    this.states.delete(state)
188  }
189
190  public start() {
191    this.breakpoints.forEach((breakpoint: Breakpoint, index) => {
192      let condition: string
193      if (index === this.breakpoints.length - 1) {
194        condition = `(${breakpoint.size}vp<=width)`
195      } else {
196        condition = `(${breakpoint.size}vp<=width<${this.breakpoints[index + 1].size}vp)`
197      }
198      breakpoint.mediaQueryListener = mediaquery.matchMediaSync(condition)
199      if (breakpoint.mediaQueryListener.matches) {
200        this.updateAllState(breakpoint.name)
201      }
202      breakpoint.mediaQueryListener.on('change', (mediaQueryResult) => {
203        if (mediaQueryResult.matches) {
204          this.updateAllState(breakpoint.name)
205        }
206      })
207    })
208  }
209
210  private updateAllState(type: BreakpointType): void {
211    this.states.forEach(state => state.update(type))
212  }
213
214  public stop() {
215    this.breakpoints.forEach((breakpoint: Breakpoint, index) => {
216      if (breakpoint.mediaQueryListener) {
217        breakpoint.mediaQueryListener.off('change')
218      }
219    })
220    this.states.clear()
221  }
222}
223
224export interface BreakpointOptions<T> {
225  xs?: T
226  sm?: T
227  md?: T
228  lg?: T
229  xl?: T
230  xxl?: T
231}
232
233export class BreakpointState<T extends Object> {
234  public value: T | undefined = undefined;
235  private options: BreakpointOptions<T>
236
237  constructor(options: BreakpointOptions<T>) {
238    this.options = options
239  }
240
241  static of<T extends Object>(options: BreakpointOptions<T>): BreakpointState<T> {
242    return new BreakpointState(options)
243  }
244
245  public update(type: BreakpointType): void {
246    if (type === 'xs') {
247      this.value = this.options.xs
248    } else if (type === 'sm') {
249      this.value = this.options.sm
250    } else if (type === 'md') {
251      this.value = this.options.md
252    } else if (type === 'lg') {
253      this.value = this.options.lg
254    } else if (type === 'xl') {
255      this.value = this.options.xl
256    } else if (type === 'xxl') {
257      this.value = this.options.xxl
258    } else {
259      this.value = undefined
260    }
261  }
262}
263```
2642.在页面中,通过媒体查询,监听应用窗口宽度变化,获取当前应用所处的断点值
265```ts
266// MediaQuerySample.ets
267import { BreakpointSystem, BreakpointState } from '../common/breakpointsystem'
268
269@Entry
270@Component
271struct MediaQuerySample {
272  @State compStr: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })
273  @State compImg: BreakpointState<Resource> = BreakpointState.of({
274    sm: $r('app.media.sm'),
275    md: $r('app.media.md'),
276    lg: $r('app.media.lg')
277  });
278
279  aboutToAppear() {
280    BreakpointSystem.getInstance().attach(this.compStr)
281    BreakpointSystem.getInstance().attach(this.compImg)
282    BreakpointSystem.getInstance().start()
283  }
284
285  aboutToDisappear() {
286    BreakpointSystem.getInstance().detach(this.compStr)
287    BreakpointSystem.getInstance().detach(this.compImg)
288    BreakpointSystem.getInstance().stop()
289  }
290
291  build() {
292    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
293      Column()
294        .height(100)
295        .width(100)
296        .backgroundImage(this.compImg.value)
297        .backgroundImagePosition(Alignment.Center)
298        .backgroundImageSize(ImageSize.Contain)
299
300      Text(this.compStr.value)
301        .fontSize(24)
302        .margin(10)
303    }
304    .width('100%')
305    .height('100%')
306  }
307}
308```
309
310
311
312## 栅格布局
313
314### 简介
315
316栅格是多设备场景下通用的辅助定位工具,通过将空间分割为有规律的栅格。栅格可以显著降低适配不同屏幕尺寸的设计及开发成本,使得整体设计和开发流程更有秩序和节奏感,同时也保证多设备上应用显示的协调性和一致性,提升用户体验。
317
318![zh-cn_image_0000001224173302](figures/zh-cn_image_0000001224173302.png)
319
320  栅格的样式由Margin、Gutter、Columns三个属性决定。
321- Margin是相对应用窗口、父容器的左右边缘的距离,决定了内容可展示的整体宽度。
322
323- Gutter是相邻的两个Column之间的距离,决定内容间的紧密程度。
324
325- Columns是栅格中的列数,其数值决定了内容的布局复杂度。
326
327单个Column的宽度是系统结合Margin、Gutter和Columns自动计算的,不需要也不允许开发者手动配置。
328
329栅格布局就是栅格结合了断点,实现栅格布局能力的组件叫栅格组件。在实际使用场景中,可以根据需要配置不同断点下栅格组件中元素占据的列数,同时也可以调整Margin、Gutter、Columns的取值,从而实现不同的布局效果。
330
331  | sm断点 | md断点 |
332| -------- | -------- |
333| ![zh-cn_image_0000001336486244](figures/zh-cn_image_0000001336486244.jpg) | ![zh-cn_image_0000001386646685](figures/zh-cn_image_0000001386646685.jpg) |
334
335> **说明:**
336> - ArkUI在API 9对栅格组件做了重构,推出了新的栅格组件[GridRow](../../reference/apis-arkui/arkui-ts/ts-container-gridrow.md)和[GridCol](../../reference/apis-arkui/arkui-ts/ts-container-gridcol.md),同时原有的[GridContainer组件](../../reference/apis-arkui/arkui-ts/ts-container-gridcontainer.md)及[栅格设置](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-grid.md)已经废弃。
337>
338> - 本文中提到的栅格组件,如无特别说明,都是指GridRow和GridCol组件。
339
340
341### 栅格组件的断点
342
343栅格组件提供了丰富的断点定制能力。
344
345**(一)开发者可以修改断点的取值范围,支持启用最多6个断点。**
346
347- 基于本文断点小节介绍的推荐值,栅格组件默认提供xs、sm、md、lg四个断点。
348
349- 栅格组件支持开发者修改断点的取值范围,除了默认的四个断点,还支持开发者启用xl和xxl两个额外的断点。
350
351> **说明:**
352> 断点并非越多越好,通常每个断点都需要开发者“精心适配”以达到最佳显示效果。
353
354**示例1:**
355
356修改默认的断点范围,同时启用xl和xxl断点。
357
358图片右下角显示了当前设备屏幕的尺寸(即应用窗口尺寸),可以看到随着窗口尺寸发生变化,栅格的断点也相应发生了改变。(为了便于理解,下图中将设备的DPI设置为160,此时1vp=1px)
359
360![window3](figures/window3.gif)
361
362
363```ts
364@Entry
365@Component
366struct GridRowSample1 {
367  @State private currentBreakpoint: string = 'unknown'
368  build() {
369    // 修改断点的取值范围同时启用更多断点,注意,修改的断点值后面必须加上vp单位。
370    GridRow({breakpoints: {value: ['600vp', '700vp', '800vp', '900vp', '1000vp'],
371      reference: BreakpointsReference.WindowSize}}) {
372      GridCol({span:{xs: 12, sm: 12, md: 12, lg:12, xl: 12, xxl:12}}) {
373        Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
374          Text(this.currentBreakpoint).fontSize(50).fontWeight(FontWeight.Medium)
375        }
376      }
377    }.onBreakpointChange((currentBreakpoint: string) => {
378      this.currentBreakpoint = currentBreakpoint
379    })
380  }
381}
382```
383
384**(二)栅格断点默认以窗口宽度为参照物,同时还允许开发者配置为以栅格组件本身的宽度为参照物。**
385
386栅格既可以用于页面整体布局的场景,也可以用于页面局部布局的场景。考虑到在实际场景中,存在应用窗口尺寸不变但是局部区域尺寸发生了变化的情况,栅格组件支持以自身宽度为参照物响应断点变化具有更大的灵活性。
387
388**示例2:**
389
390以栅格组件宽度为参考物响应断点变化。满足窗口尺寸不变,而部分内容区需要做响应式变化的场景。
391
392为了便于理解,下图中自定义预览器的设备屏幕宽度设置为650vp。示例代码中将侧边栏的变化范围控制在[100vp, 600vp],那么右侧的栅格组件宽度相对应在[550vp, 50vp]之间变化。根据代码中对栅格断点的配置,栅格组件宽度发生变化时,其断点相应的发生改变。
393
394![component](figures/component_example2.gif)
395
396
397```ts
398@Entry
399@Component
400struct GridRowSample2 {
401  @State private currentBreakpoint: string = 'unknown';
402  build() {
403    // 用户可以通过拖拽侧边栏组件中的分隔线,调整侧边栏和内容区的宽度。
404    SideBarContainer(SideBarContainerType.Embed)
405    {
406      // 侧边栏,尺寸变化范围 [100vp, 600vp]
407      Column(){}.width('100%').backgroundColor('#19000000')
408
409      // 内容区,尺寸变化范围 [550vp, 50vp]
410      GridRow({breakpoints: {value: ['100vp', '200vp', '300vp', '400vp', '500vp'],
411        reference: BreakpointsReference.ComponentSize}}) {
412        GridCol({span:{xs: 12, sm: 12, md: 12, lg:12, xl: 12, xxl:12}}) {
413          Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
414            Text(this.currentBreakpoint).fontSize(50).fontWeight(FontWeight.Medium)
415          }
416        }
417      }.onBreakpointChange((currentBreakpoint: string) => {
418        this.currentBreakpoint = currentBreakpoint;
419      }).width('100%')
420    }
421    // 侧边栏拖拽到最小宽度时,不自动隐藏
422    .autoHide(false)
423    .sideBarWidth(100)
424    // 侧边栏的最小宽度
425    .minSideBarWidth(100)
426    // 侧边栏的最大宽度
427    .maxSideBarWidth(600)
428  }
429}
430```
431
432**(三)栅格组件的断点发生变化时,会通过onBreakPointChange事件通知开发者。**
433
434在之前的两个例子中,已经演示了onBreakpointChange事件的用法,此处不再赘述。
435
436
437### 栅格组件的columns、gutter和margin
438
439
440栅格组件columns默认为12列,gutter默认为0,同时支持开发者根据实际需要定义不同断点下的columns数量以及gutter长度。特别的,在栅格组件实际使用过程中,常常会发生多个元素占据的列数相加超过总列数而折行的场景。栅格组件还允许开发者分别定义水平方向的gutter(相邻两列之间的间距)和垂直方向的gutter(折行时相邻两行之间的间距)。
441
442
443  考虑到[组件通用属性](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)中已经有margin和padding,栅格组件不再单独提供额外的margin属性,直接使用通用属性即可。借助margin或者padding属性,均可以控制栅格组件与父容器左右边缘的距离,但是二者也存在一些差异:
444- margin区域在栅格组件的边界外,padding区域在栅格组件的边界内。
445
446- 栅格组件的backgroundColor会影响padding区域,但不会影响margin区域。
447
448
449总的来讲,margin在组件外而padding在组件内,开发者可以根据实际需要进行选择及实现目标效果。
450
451**示例3:**
452
453
454不同断点下,定义不同的columns和gutter。
455
456
457  | sm | md | lg |
458| -------- | -------- | -------- |
459| ![zh-cn_image_0000001386807405](figures/zh-cn_image_0000001386807405.jpg) | ![zh-cn_image_0000001336167540](figures/zh-cn_image_0000001336167540.jpg) | ![zh-cn_image_0000001386487457](figures/zh-cn_image_0000001386487457.jpg) |
460
461
462
463```ts
464@Entry
465@Component
466struct GridRowSample3 {
467  private bgColors: ResourceColor[] = [
468     $r('sys.color.ohos_id_color_palette_aux1'),
469     $r('sys.color.ohos_id_color_palette_aux2'),
470     $r('sys.color.ohos_id_color_palette_aux3'),
471     $r('sys.color.ohos_id_color_palette_aux4'),
472     $r('sys.color.ohos_id_color_palette_aux5'),
473     $r('sys.color.ohos_id_color_palette_aux6')
474  ]
475  build() {
476    // 配置不同断点下columns和gutter的取值
477    GridRow({columns: {sm: 4, md: 8, lg: 12},
478      gutter: {x: {sm: 8, md: 16, lg: 24}, y: {sm: 8, md: 16, lg: 24}}}) {
479      ForEach(this.bgColors, (bgColor:ResourceColor)=>{
480        GridCol({span: {sm: 2, md: 2, lg: 2}}) {
481          Row().backgroundColor(bgColor).height(30).width('100%')
482        }
483      })
484    }
485  }
486}
487```
488
489
490**示例4:**
491
492
493通过通用属性margin或者padding,均可以控制栅格组件与其父容器左右两侧的距离,但padding区域计算在栅格组件内而margin区域计算在栅格组件外。此外,借助onBreakpointChange事件,还可以改变不同断点下margin或padding值。
494
495
496![zh-cn_image_0000001336327452](figures/zh-cn_image_0000001336327452.png)
497
498
499
500```ts
501@Entry
502@Component
503struct GridRowSample4 {
504  @State private gridMargin: number = 0
505  build() {
506    Column() {
507      Row().width('100%').height(30)
508
509      // 使用padding控制栅格左右间距
510      GridRow() {
511        GridCol({span:{xs: 12, sm: 12, md: 12, lg:12}}) {
512          Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
513            Text("padding").fontSize(24).fontWeight(FontWeight.Medium)
514          }.backgroundColor('#19000000').width('100%')
515        }
516      }
517      .height(50)
518      .borderWidth(2)
519      .borderColor('#F1CCB8')
520      .padding({left: this.gridMargin, right: this.gridMargin})
521      // 借助断点变化事件配置不同断点下栅格组件的左右间距值
522      .onBreakpointChange((currentBreakpoint: string) => {
523        if (currentBreakpoint === 'lg' || currentBreakpoint === 'md') {
524          this.gridMargin = 24
525        } else {
526          this.gridMargin = 12
527        }
528      })
529
530      Row().width('100%').height(30)
531
532      // 使用margin控制栅格左右间距
533      GridRow() {
534        GridCol({span:{xs: 12, sm: 12, md: 12, lg:12}}) {
535          Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
536            Text("margin").fontSize(24).fontWeight(FontWeight.Medium)
537          }.backgroundColor('#19000000').width('100%')
538        }
539      }
540      .height(50)
541      .borderWidth(2)
542      .borderColor('#F1CCB8')
543      .margin({left: this.gridMargin, right: this.gridMargin})
544    }
545  }
546}
547```
548
549
550### 栅格组件的span、offset和order
551
552
553栅格组件(GridRow)的直接孩子节点只可以是栅格子组件(GridCol),GridCol组件支持配置span、offset和order三个参数。这三个参数的取值按照"xs -&gt; sm -&gt; md -&gt; lg -&gt; xl -&gt; xxl"的向后方向具有继承性(不支持向前方向的继承性),例如将sm断点下span的值配置为3,不配置md断点下span的值,则md断点下span的取值也是3。
554
555
556  | 参数名 | 类型 | 必填 | 默认值 | 说明 |
557| -------- | -------- | -------- | -------- | -------- |
558| span | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 是 | - | 在栅格中占据的列数。span为0,意味着该元素既不参与布局计算,也不会被渲染。 |
559| offset | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 否 | 0 | 相对于前一个栅格子组件偏移的列数。 |
560| order | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 否 | 0 | 元素的序号,根据栅格子组件的序号,从小到大对栅格子组件做排序。 |
561
562
563**示例5:**
564
565
566通过span参数配置GridCol在不同断点下占据不同的列数。特别的,将md断点下3和6的span配置为0,这样在md断点下3和6不会渲染和显示。
567
568
569  | sm | md | lg |
570| -------- | -------- | -------- |
571| ![zh-cn_image_0000001336487884](figures/zh-cn_image_0000001336487884.jpg) | ![zh-cn_image_0000001386648317](figures/zh-cn_image_0000001386648317.jpg) | ![zh-cn_image_0000001386327997](figures/zh-cn_image_0000001386327997.jpg) |
572
573
574
575```ts
576class Obj {
577  public index: number = 1;
578  public color: Resource = $r('sys.color.ohos_id_color_palette_aux1')
579}
580@Entry
581@Component
582struct GridRowSample5 {
583  private elements: Obj[] = [
584    {index: 1, color: $r('sys.color.ohos_id_color_palette_aux1')},
585    {index: 2, color: $r('sys.color.ohos_id_color_palette_aux2')},
586    {index: 3, color: $r('sys.color.ohos_id_color_palette_aux3')},
587    {index: 4, color: $r('sys.color.ohos_id_color_palette_aux4')},
588    {index: 5, color: $r('sys.color.ohos_id_color_palette_aux5')},
589    {index: 6, color: $r('sys.color.ohos_id_color_palette_aux6')},
590  ]
591
592  build() {
593    GridRow() {
594      ForEach(this.elements, (item:Obj)=>{
595        GridCol({span: {sm: 6, md: (item.index % 3 === 0) ? 0 : 4, lg: 3}}) {
596          Row() {
597            Text('' + item.index).fontSize(24)
598          }
599          .justifyContent(FlexAlign.Center)
600          .backgroundColor(item.color).height(30).width('100%')
601        }
602      })
603    }
604  }
605}
606```
607
608
609**示例6:**
610
611
612通过offset参数,配置GridCol相对其前一个兄弟间隔的列数。
613
614
615  | sm | md | lg |
616| -------- | -------- | -------- |
617| ![zh-cn_image_0000001386807873](figures/zh-cn_image_0000001386807873.jpg) | ![zh-cn_image_0000001336168020](figures/zh-cn_image_0000001336168020.jpg) | ![zh-cn_image_0000001386487913](figures/zh-cn_image_0000001386487913.jpg) |
618
619
620
621```ts
622class Obj {
623  public index: number = 1;
624  public color: Resource = $r('sys.color.ohos_id_color_palette_aux1')
625}
626@Entry
627@Component
628struct GridRowSample6 {
629  private elements: Obj[] = [
630    {index: 1, color: $r('sys.color.ohos_id_color_palette_aux1')},
631    {index: 2, color: $r('sys.color.ohos_id_color_palette_aux2')},
632    {index: 3, color: $r('sys.color.ohos_id_color_palette_aux3')},
633    {index: 4, color: $r('sys.color.ohos_id_color_palette_aux4')},
634    {index: 5, color: $r('sys.color.ohos_id_color_palette_aux5')},
635    {index: 6, color: $r('sys.color.ohos_id_color_palette_aux6')},
636  ]
637
638  build() {
639    GridRow() {
640      ForEach(this.elements, (item:Obj)=>{
641        GridCol({span: {sm: 6, md: 4, lg: 3}, offset: {sm: 0, md: 2, lg: 1} }) {
642          Row() {
643            Text('' + item.index).fontSize(24)
644          }
645          .justifyContent(FlexAlign.Center)
646          .backgroundColor(item.color).height(30).width('100%')
647        }
648      })
649    }
650  }
651}
652```
653
654
655**示例7:**
656
657
658通过order属性,控制GridCol的顺序。在sm和md断点下,按照1至6的顺序排列显示;在lg断点下,按照6至1的顺序排列显示。
659
660
661  | sm | md | lg |
662| -------- | -------- | -------- |
663| ![zh-cn_image_0000001336327916](figures/zh-cn_image_0000001336327916.jpg) | ![zh-cn_image_0000001336008356](figures/zh-cn_image_0000001336008356.jpg) | ![zh-cn_image_0000001336487888](figures/zh-cn_image_0000001336487888.jpg) |
664
665
666
667```ts
668class Obj {
669  public index: number = 1;
670  public color: Resource = $r('sys.color.ohos_id_color_palette_aux1')
671}
672@Entry
673@Component
674struct GridRowSample7 {
675  private elements: Obj[] = [
676    {index: 1, color: $r('sys.color.ohos_id_color_palette_aux1')},
677    {index: 2, color: $r('sys.color.ohos_id_color_palette_aux2')},
678    {index: 3, color: $r('sys.color.ohos_id_color_palette_aux3')},
679    {index: 4, color: $r('sys.color.ohos_id_color_palette_aux4')},
680    {index: 5, color: $r('sys.color.ohos_id_color_palette_aux5')},
681    {index: 6, color: $r('sys.color.ohos_id_color_palette_aux6')},
682  ]
683
684  build() {
685    GridRow() {
686      ForEach(this.elements, (item:Obj)=>{
687        GridCol({span: {sm: 6, md: 4, lg: 3}, order: {lg: (6-item.index)}}) {
688          Row() {
689            Text('' + item.index).fontSize(24)
690          }
691          .justifyContent(FlexAlign.Center)
692          .backgroundColor(item.color).height(30).width('100%')
693        }
694      })
695    }
696  }
697}
698```
699
700
701**示例8:**
702
703
704仅配置sm和lg断点下span、offset和order参数的值,则md断点下这三个参数的取值与sm断点相同(按照“sm-&gt;md-&gt;lg”的向后方向继承)。
705
706
707  | sm | md | lg |
708| -------- | -------- | -------- |
709| ![zh-cn_image_0000001386648321](figures/zh-cn_image_0000001386648321.jpg) | ![zh-cn_image_0000001386328001](figures/zh-cn_image_0000001386328001.jpg) | ![zh-cn_image_0000001386807877](figures/zh-cn_image_0000001386807877.jpg) |
710
711
712
713```ts
714class Obj {
715  public index: number = 1;
716  public color: Resource = $r('sys.color.ohos_id_color_palette_aux1')
717}
718@Entry
719@Component
720struct GridRowSample8 {
721  private elements: Obj[] = [
722    {index: 1, color: $r('sys.color.ohos_id_color_palette_aux1')},
723    {index: 2, color: $r('sys.color.ohos_id_color_palette_aux2')},
724    {index: 3, color: $r('sys.color.ohos_id_color_palette_aux3')},
725    {index: 4, color: $r('sys.color.ohos_id_color_palette_aux4')},
726    {index: 5, color: $r('sys.color.ohos_id_color_palette_aux5')},
727    {index: 6, color: $r('sys.color.ohos_id_color_palette_aux6')},
728  ]
729  build() {
730    GridRow() {
731      ForEach(this.elements, (item:Obj)=>{
732        // 不配置md断点下三个参数的值,则其取值与sm断点相同
733        GridCol({span: {sm:4, lg: 3}, offset: {sm: 2, lg: 1},
734          order: {sm: (6-item.index), lg: item.index}}) {
735          Row() {
736            Text('' + item.index).fontSize(24)
737          }
738          .justifyContent(FlexAlign.Center)
739          .backgroundColor(item.color).height(30).width('100%')
740        }
741      })
742    }
743  }
744}
745```
746
747
748### 栅格组件的嵌套使用
749
750栅格组件可以嵌套使用以满足复杂场景的需要。
751
752**示例9:**
753
754  | sm | md | lg |
755| -------- | -------- | -------- |
756| ![zh-cn_image_0000001336338670](figures/zh-cn_image_0000001336338670.jpg) | ![zh-cn_image_0000001336019094](figures/zh-cn_image_0000001336019094.jpg) | ![zh-cn_image_0000001336498646](figures/zh-cn_image_0000001336498646.jpg) |
757
758
759```ts
760class Obj {
761  public index: number = 1;
762  public color: Resource = $r('sys.color.ohos_id_color_palette_aux1')
763}
764@Entry
765@Component
766struct GridRowSample9 {
767  private elements: Obj[] = [
768    {index: 1, color: $r('sys.color.ohos_id_color_palette_aux1')},
769    {index: 2, color: $r('sys.color.ohos_id_color_palette_aux2')},
770    {index: 3, color: $r('sys.color.ohos_id_color_palette_aux3')},
771    {index: 4, color: $r('sys.color.ohos_id_color_palette_aux4')},
772    {index: 5, color: $r('sys.color.ohos_id_color_palette_aux5')},
773    {index: 6, color: $r('sys.color.ohos_id_color_palette_aux6')},
774  ]
775  build() {
776    GridRow() {
777      GridCol({span: {sm: 12, md: 10, lg: 8}, offset: {sm: 0, md: 1, lg: 2}}) {
778        GridRow() {
779          ForEach(this.elements, (item:Obj)=>{
780            GridCol({span: {sm: 6, md: 4, lg: 3}}) {
781              Row() {
782                Text('' + item.index).fontSize(24)
783              }
784              .justifyContent(FlexAlign.Center)
785              .backgroundColor(item.color).height(30).width('100%')
786            }
787          })
788        }
789        .backgroundColor('#19000000')
790        .height('100%')
791      }
792    }
793  }
794}
795```
796
797
798### 总结
799
800如前所述,栅格组件提供了丰富的自定义能力,功能异常灵活和强大。只需要明确栅格在不同断点下的Columns、Margin、Gutter及span等参数,即可确定最终布局,无需关心具体的设备类型及设备状态(如横竖屏)等。栅格可以节约设计团队与开发团队的沟通成本,提升整体开发效率。
801
802