鸿蒙UI(ArkUI-方舟UI框架)-开辟布局

打印 上一主题 下一主题

主题 829|帖子 829|积分 2487

开辟布局

1、布局概述

1)布局布局


2)布局元素组成


3)怎样选择布局

声明式UI提供了以下10种常见布局,开辟者可根据实际应用场景选择合适的布局举行页面开辟。
布局应用场景线性布局(Row、Column)如果布局内子元素超过1个时,且可以或许以某种方式线性分列时优先考虑此布局层叠布局(Stack)组件必要有堆叠效果时优先考虑此布局。层叠布局的堆叠效果不会占用或影响其他同容器内子组件的布局空间。比方Panel作为子组件弹出时将其他组件覆盖更为公道,则优先考虑在外层利用堆叠布局。弹性布局(Flex)弹性布局是与线性布局类似的布局方式。区别在于弹性布局默认可以或许使子组件压缩或拉伸。在子组件必要计算拉伸或压缩比例时优先利用此布局,可使得多个容器内子组件能有更好的视觉上的填充效果。相对布局(RelativeContainer)相对布局是在二维空间中的布局方式,不必要依照线性布局的规则,布局方式更为自由。通过在子组件上设置锚点规则(AlignRules)使子组件可以或许将本身在横轴、纵轴中的位置与容器或容器内其他子组件的位置对齐。设置的锚点规则可以自然支持子元素压缩、拉伸、堆叠或形成多行效果。在页面元素分布复杂或通过线性布局会使容器嵌套层数过深时推荐利用。栅格布局(GridRow、GridCol)栅格是多设备场景下通用的辅助定位工具,可将空间分割为有规律的栅格。栅格不同于网格布局固定的空间划分,可以实现不同设备下不同的布局,空间划分更随心所欲,从而显著降低适配不同屏幕尺寸的设计及开辟本钱,使得团体设计和开辟流程更有秩序和节奏感,同时也保证多设备上应用显示的调和性和同等性,提升用户体验。推荐内容相同但布局不同时利用。媒体查询(@ohos.mediaquery)媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。比方根据设备和应用的不同属性信息设计不同的布局,以及屏幕发生动态改变时更新应用的页面布局。列表(List)利用列表可以高效地显示布局化、可滚动的信息。在ArkUI中,列表具有垂直和水平布局能力和自适应交错轴方向上分列个数的布局能力,超出屏幕时可以滚动。列表得当用于呈现同类数据类型或数据类型集,比方图片和文本。网格(Grid)网格布局具有较强的页面均分能力、子元素占比控制能力。网格布局可以控制元素所占的网格数量、设置子元素横跨几行大概几列,当网格容器尺寸发生变化时,所有子元素以及间距等比例调整。推荐在必要按照固定比例大概均匀分配空间的布局场景下利用,比方计算器、相册、日历等。轮播(Swiper)轮播组件通常用于实现广告轮播、图片预览等。选项卡(Tabs)选项卡可以在一个页面内快速实现视图内容的切换,一方面提升查找信息的效率,另一方面精简用户单次获取到的信息量。 4)布局位置

position、offset等属性影响告终构容器相对于自身或其他组件的位置。
定位能力利用场景实现方法绝对定位对于不同尺寸的设备,利用绝对定位的适应性会比较差,在屏幕的适配上有缺陷利用position实现绝对定位,设置元素左上角相对于父容器左上角偏移位置。在布局容器中,设置该属性不影响父容器布局,仅在绘制时举行位置调整。相对定位相对定位不脱离文档流,即原位置依然保留,不影响元素本身的特性,仅相对于原位置举行偏移。利用offset可以实现相对定位,设置元素相对于自身的偏移量。设置该属性,不影响父容器布局,仅在绘制时举行位置调整。 5)对子元素的约束



  • 拉伸:容器组件尺寸发生变化时,增长或减小的空间全部分配给容器组件内指定区域

  • 缩放:子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比稳固

  • 占比:子组件的宽高按照预设的比例,随先人容器组件发生变化

  • 隐蔽:隐蔽能力是指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐蔽,其中相同显示优先级的子组件同时显示或隐蔽

2、构建布局

1)线性布局 (Row/Column)

概述

  1. 线性布局(LinearLayout)是开发中最常用的布局,通过线性容器Row和Column构建。线性布局是其他布局的基础,其子元素在线性方向上(水平方向和垂直方向)依次排列。线性布局的排列方向由所选容器组件决定,Column容器内子元素按照垂直方向排列,Row容器内子元素按照水平方向排列。根据不同的排列方向,开发者可选择使用Row或Column容器创建线性布局。
复制代码
图1 Column(列,竖着走)容器内子元素分列表示图

图2 Row(行,横着走)容器内子元素分列表示图

布局子元素在分列方向上的间距

在布局容器内,可以通过space属性设置分列方向上子元素的间距,使各子元素在分列方向上有等间距效果。


  • Column容器内分列方向上的间距
    1. Column({ space: 20 }) {
    2.   Text('space: 20').fontSize(15).fontColor(Color.Gray).width('90%')
    3.   Row().width('90%').height(50).backgroundColor(0xF5DEB3)
    4.   Row().width('90%').height(50).backgroundColor(0xD2B48C)
    5.   Row().width('90%').height(50).backgroundColor(0xF5DEB3)
    6. }.width('100%')
    复制代码

  • Row容器内分列方向上的间距
    1. Row({ space: 35 }) {
    2.   Text('space: 35').fontSize(15).fontColor(Color.Gray)
    3.   Row().width('10%').height(150).backgroundColor(0xF5DEB3)
    4.   Row().width('10%').height(150).backgroundColor(0xD2B48C)
    5.   Row().width('10%').height(150).backgroundColor(0xF5DEB3)
    6. }.width('90%')
    复制代码

布局子元素在交错轴上的对齐方式(alignItems)

在布局容器内,可以通过alignItems属性设置子元素在交错轴(分列方向的垂直方向)上的对齐方式。
  1. Column({}) {
  2. }.width('100%').alignItems(HorizontalAlign.Start)
复制代码
其中,交错轴为垂直方向时,取值为VerticalAlign类型,水平方向取值为HorizontalAlign类型。
  1. Column({}) {
  2. }.width('100%').alignItems(VerticalAlign.Start)
复制代码


  • HorizontalAlign.Start:子元素在水平方向左对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').alignItems(HorizontalAlign.Start).backgroundColor('rgb(242,242,242)')
    复制代码

  • HorizontalAlign.Center:子元素在水平方向居中对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').alignItems(HorizontalAlign.Center).backgroundColor('rgb(242,242,242)')
    复制代码



  • HorizontalAlign.End:子元素在水平方向右对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').alignItems(HorizontalAlign.End).backgroundColor('rgb(242,242,242)')
    复制代码



  • VerticalAlign.Top:子元素在垂直方向顶部对齐。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).alignItems(VerticalAlign.Top).backgroundColor('rgb(242,242,242)')
    复制代码

  • VerticalAlign.Center:子元素在垂直方向居中对齐。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).alignItems(VerticalAlign.Center).backgroundColor('rgb(242,242,242)')
    复制代码

  • VerticalAlign.Bottom:子元素在垂直方向底部对齐。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).alignItems(VerticalAlign.Bottom).backgroundColor('rgb(242,242,242)')
    复制代码

布局子元素在主轴上的分列方式(justifyContent)

在布局容器内,可以通过justifyContent属性设置子元素在容器主轴上的分列方式。
可以从主轴起始位置开始排布,也可以从主轴竣事位置开始排布,大概均匀分割主轴的空间。
1、Column容器内子元素在垂直方向上的分列



  • justifyContent(FlexAlign.Start):元素在垂直方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.Start)
    复制代码

  • justifyContent(FlexAlign.Center):元素在垂直方向中心对齐,第一个元素与行首的距离与最后一个元素与行尾距离相同。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.Center)
    复制代码

  • justifyContent(FlexAlign.End):元素在垂直方向尾部对齐,最后一个元素与行尾对齐,其他元素与后一个对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.End)
    复制代码

  • justifyContent(FlexAlign.SpaceBetween):垂直方向均匀分配元素,相邻元素之间距离相同。第一个元素与行首对齐,最后一个元素与行尾对齐。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceBetween)
    复制代码



  • justifyContent(FlexAlign.SpaceAround):垂直方向均匀分配元素,相邻元素之间距离相同。第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceAround)
    复制代码



  • justifyContent(FlexAlign.SpaceEvenly):垂直方向均匀分配元素,相邻元素之间的距离、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。
    1. Column({}) {
    2.   Column() {
    3.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('80%').height(50).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('80%').height(50).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(300).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceEvenly)
    复制代码

    1、Row容器内子元素在水平方向上的分列

  • justifyContent(FlexAlign.Start):元素在水平方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.Start)
    复制代码

  • justifyContent(FlexAlign.Center):元素在水平方向中心对齐,第一个元素与行首的距离与最后一个元素与行尾距离相同。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.Center)
    复制代码

  • justifyContent(FlexAlign.End):元素在水平方向尾部对齐,最后一个元素与行尾对齐,其他元素与后一个对齐
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.End)
    复制代码

  • justifyContent(FlexAlign.SpaceBetween):水平方向均匀分配元素,相邻元素之间距离相同。第一个元素与行首对齐,最后一个元素与行尾对齐。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceBetween)
    复制代码

  • justifyContent(FlexAlign.SpaceAround):水平方向均匀分配元素,相邻元素之间距离相同。第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceAround)
    复制代码

  • justifyContent(FlexAlign.SpaceEvenly):水平方向均匀分配元素,相邻元素之间的距离、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。
    1. Row({}) {
    2.   Column() {
    3.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    4.   Column() {
    5.   }.width('20%').height(30).backgroundColor(0xD2B48C)
    6.   Column() {
    7.   }.width('20%').height(30).backgroundColor(0xF5DEB3)
    8. }.width('100%').height(200).backgroundColor('rgb(242,242,242)').justifyContent(FlexAlign.SpaceEvenly)
    复制代码

自适应拉伸

在线性布局下,常用空缺填充组件Blank,在容器主轴方向主动填充空缺空间,达到自适应拉伸效果。Row和Column作为容器,只必要添加宽高为百分比,当屏幕宽高发生变化时,会产生自适应效果。
  1. @Entry
  2. @Component
  3. struct BlankExample {
  4.   build() {
  5.     Column() {
  6.       Row() {
  7.         Text('Bluetooth').fontSize(18)
  8.         Blank()
  9.         Toggle({ type: ToggleType.Switch, isOn: true })
  10.       }.backgroundColor(0xFFFFFF).borderRadius(15).padding({ left: 12 }).width('100%')
  11.     }.backgroundColor(0xEFEFEF).padding(20).width('100%')
  12.   }
  13. }
复制代码
竖屏

横屏

自适应缩放

自适应缩放是指子元素随容器尺寸的变化而按照预设的比例主动调整尺寸,适应各种不同巨细的设备。在线性布局中,可以利用以下两种方法实现自适应缩放。


  • 父容器尺寸确定时,利用layoutWeight属性设置子元素和兄弟元素在主轴上的权重,忽略元素本身尺寸设置,使它们在任意尺寸的设备下自适应占满剩余空间。
    1. @Entry
    2. @Component
    3. struct layoutWeightExample {
    4.   build() {
    5.     Column() {
    6.       Text('1:2:3').width('100%')
    7.       Row() {
    8.         Column() {
    9.           Text('layoutWeight(1)')
    10.             .textAlign(TextAlign.Center)
    11.         }.layoutWeight(1).backgroundColor(0xF5DEB3).height('100%')
    12.         Column() {
    13.           Text('layoutWeight(2)')
    14.             .textAlign(TextAlign.Center)
    15.         }.layoutWeight(2).backgroundColor(0xD2B48C).height('100%')
    16.         Column() {
    17.           Text('layoutWeight(3)')
    18.             .textAlign(TextAlign.Center)
    19.         }.layoutWeight(3).backgroundColor(0xF5DEB3).height('100%')
    20.       }.backgroundColor(0xffd306).height('30%')
    21.       Text('2:5:3').width('100%')
    22.       Row() {
    23.         Column() {
    24.           Text('layoutWeight(2)')
    25.             .textAlign(TextAlign.Center)
    26.         }.layoutWeight(2).backgroundColor(0xF5DEB3).height('100%')
    27.         Column() {
    28.           Text('layoutWeight(5)')
    29.             .textAlign(TextAlign.Center)
    30.         }.layoutWeight(5).backgroundColor(0xD2B48C).height('100%')
    31.         Column() {
    32.           Text('layoutWeight(3)')
    33.             .textAlign(TextAlign.Center)
    34.         }.layoutWeight(3).backgroundColor(0xF5DEB3).height('100%')
    35.       }.backgroundColor(0xffd306).height('30%')
    36.     }
    37.   }
    38. }
    复制代码
    横屏

    竖屏

  • 父容器尺寸确定时,利用百分比设置子元素和兄弟元素的宽度,使他们在任意尺寸的设备下保持固定的自适应占比。
    1. @Entry
    2. @Component
    3. struct WidthExample {
    4.   build() {
    5.     Column() {
    6.       Row() {
    7.         Column() {
    8.           Text('left width 20%')
    9.             .textAlign(TextAlign.Center)
    10.         }.width('20%').backgroundColor(0xF5DEB3).height('100%')
    11.         Column() {
    12.           Text('center width 50%')
    13.             .textAlign(TextAlign.Center)
    14.         }.width('50%').backgroundColor(0xD2B48C).height('100%')
    15.         Column() {
    16.           Text('right width 30%')
    17.             .textAlign(TextAlign.Center)
    18.         }.width('30%').backgroundColor(0xF5DEB3).height('100%')
    19.       }.backgroundColor(0xffd306).height('30%')
    20.     }
    21.   }
    22. }
    复制代码
    竖屏:

    横屏:

自适应延伸

自适应延伸是指在不同尺寸设备下,当页面的内容超出屏幕巨细而无法完全显示时,可以通过滚动条举行拖动展示。这种方法适用于线性布局中内容无法一屏展示的场景。通常有以下两种实现方式。


  • 在List中添加滚动条:当List子项过多一屏放不下时,可以将每一项子元素放置在不同的组件中,通过滚动条举行拖动展示。可以通过scrollBar属性设置滚动条的常驻状态,edgeEffect属性设置拖动到内容最末端的回弹效果。
  • 利用Scroll组件:在线性布局中,开辟者可以举行垂直方向大概水平方向的布局。当一屏无法完全显示时,可以在Column或Row组件的外层包裹一个可滚动的容器组件Scroll来实现可滑动的线性布局。
    垂直方向布局中利用Scroll组件:
    1. @Entry
    2. @Component
    3. struct ScrollExample {
    4.   scroller: Scroller = new Scroller();
    5.   private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    6.   build() {
    7.     Scroll(this.scroller) {
    8.       Column() {
    9.         ForEach(this.arr, (item?:number|undefined) => {
    10.           if(item){
    11.             Text(item.toString())
    12.             .width('90%')
    13.             .height(150)
    14.             .backgroundColor(0xFFFFFF)
    15.             .borderRadius(15)
    16.             .fontSize(16)
    17.             .textAlign(TextAlign.Center)
    18.             .margin({ top: 10 })
    19.           }
    20.         }, (item:number) => item.toString())
    21.       }.width('100%')
    22.     }
    23.     .backgroundColor(0xDCDCDC)
    24.     .scrollable(ScrollDirection.Vertical) // 滚动方向为垂直方向
    25.     .scrollBar(BarState.On) // 滚动条常驻显示
    26.     .scrollBarColor(Color.Gray) // 滚动条颜色
    27.     .scrollBarWidth(10) // 滚动条宽度
    28.     .edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
    29.   }
    30. }
    复制代码
    水平方向布局中利用Scroll组件:
    1. @Entry
    2. @Component
    3. struct ScrollExample {
    4.   scroller: Scroller = new Scroller();
    5.   private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    6.   build() {
    7.     Scroll(this.scroller) {
    8.       Row() {
    9.         ForEach(this.arr, (item?:number|undefined) => {
    10.           if(item){
    11.             Text(item.toString())
    12.             .height('90%')
    13.             .width(150)
    14.             .backgroundColor(0xFFFFFF)
    15.             .borderRadius(15)
    16.             .fontSize(16)
    17.             .textAlign(TextAlign.Center)
    18.             .margin({ left: 10 })
    19.           }
    20.         })
    21.       }.height('100%')
    22.     }
    23.     .backgroundColor(0xDCDCDC)
    24.     .scrollable(ScrollDirection.Horizontal) // 滚动方向为水平方向
    25.     .scrollBar(BarState.On) // 滚动条常驻显示
    26.     .scrollBarColor(Color.Gray) // 滚动条颜色
    27.     .scrollBarWidth(10) // 滚动条宽度
    28.     .edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
    29.   }
    30. }
    复制代码
2)层叠布局 (Stack)

概述

层叠布局(StackLayout)用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。
层叠布局具有较强的页面层叠、位置定位能力,其利用场景有广告、卡片层叠效果等。
开辟布局

Stack组件为容器组件,容器内可包含各种子元素。其中子元素默认举行居中堆叠。子元素被约束在Stack下,举行本身的样式定义以及分列。
  1. // xxx.ets
  2. let MTop:Record<string,number> = { 'top': 50 }
  3. @Entry
  4. @Component
  5. struct StackExample {
  6.   build() {
  7.     Column(){
  8.       Stack({ }) {
  9.         Column(){}.width('90%').height('100%').backgroundColor('#ff58b87c')
  10.         Text('text').width('60%').height('60%').backgroundColor('#ffc3f6aa')
  11.         Button('button').width('30%').height('30%').backgroundColor('#ff8ff3eb').fontColor('#000')
  12.       }.width('100%').height(150).margin(MTop)
  13.     }
  14.   }
  15. }
复制代码

对齐方式

Stack组件通过alignContent参数实现位置的相对移动。如图2所示,支持九种对齐方式。

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct StackExample {
  5.   build() {
  6.     Stack({ alignContent: Alignment.TopStart }) {
  7.       Text('Stack').width('90%').height('100%').backgroundColor('#e1dede').align(Alignment.BottomEnd)
  8.       Text('Item 1').width('70%').height('80%').backgroundColor(0xd2cab3).align(Alignment.BottomEnd)
  9.       Text('Item 2').width('50%').height('60%').backgroundColor(0xc1cbac).align(Alignment.BottomEnd)
  10.     }.width('100%').height(150).margin({ top: 5 })
  11.   }
  12. }
复制代码
Z序控制

Stack容器中兄弟组件显示层级关系可以通过Z序控制的zIndex属性改变。zIndex值越大,显示层级越高,即zIndex值大的组件会覆盖在zIndex值小的组件上方
  1. Stack({ alignContent: Alignment.BottomStart }) {
  2.   Column() {
  3.     Text('Stack子元素1').fontSize(20)
  4.   }.width(100).height(100).backgroundColor(0xffd306).zIndex(2)
  5.   Column() {
  6.     Text('Stack子元素2').fontSize(20)
  7.   }.width(150).height(150).backgroundColor(Color.Pink).zIndex(1)
  8.   Column() {
  9.     Text('Stack子元素3').fontSize(20)
  10.   }.width(200).height(200).backgroundColor(Color.Grey)
  11. }.width(350).height(350).backgroundColor(0xe0e0e0)
复制代码

场景实例

利用层叠布局快速搭建页面。
  1. @Entry
  2. @Component
  3. struct StackSample {
  4.   private arr: string[] = ['APP1', 'APP2', 'APP3', 'APP4', 'APP5', 'APP6', 'APP7', 'APP8'];
  5.   build() {
  6.     Stack({ alignContent: Alignment.Bottom }) {
  7.       Flex({ wrap: FlexWrap.Wrap }) {
  8.         ForEach(this.arr, (item:string) => {
  9.           Text(item)
  10.             .width(100)
  11.             .height(100)
  12.             .fontSize(16)
  13.             .margin(10)
  14.             .textAlign(TextAlign.Center)
  15.             .borderRadius(10)
  16.             .backgroundColor(0xFFFFFF)
  17.         }, (item:string):string => item)
  18.       }.width('100%').height('100%')
  19.       Flex({ justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
  20.         Text('联系人').fontSize(16)
  21.         Text('设置').fontSize(16)
  22.         Text('短信').fontSize(16)
  23.       }
  24.       .width('50%')
  25.       .height(50)
  26.       .backgroundColor('#16302e2e')
  27.       .margin({ bottom: 15 })
  28.       .borderRadius(15)
  29.     }.width('100%').height('100%').backgroundColor('#CFD0CF')
  30.   }
  31. }
复制代码

3)弹性布局(Flex)

概述

弹性布局(Flex)提供更加有效的方式对容器中的子元素举行分列、对齐和分配剩余空间。常用于页面头部导航栏的均匀分布、页面框架的搭建、多行数据的分列等。
容器默认存在主轴与交错轴,子元素默认沿主轴分列,子元素在主轴方向的尺寸称为主轴尺寸,在交错轴方向的尺寸称为交错轴尺寸。

根本概念



  • 主轴:Flex组件布局方向的轴线,子元素默认沿着主轴分列。主轴开始的位置称为主轴起始点,竣事位置称为主轴竣事点。
  • 交错轴:垂直于主轴方向的轴线。交错轴开始的位置称为交错轴起始点,竣事位置称为交错轴竣事点。
布局方向

在弹性布局中,容器的子元素可以按照任意方向分列。通过设置参数direction,可以决定主轴的方向,从而控制子元素的分列方向。



  • FlexDirection.Row(默认值):主轴为水平方向,子元素从起始端沿着水平方向开始排布。
    1. Flex({ direction: FlexDirection.Row }) {
    2.   Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .height(70)
    7. .width('90%')
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • FlexDirection.RowReverse:主轴为水平方向,子元素从终点端沿着FlexDirection. Row相反的方向开始排布。
    1. Flex({ direction: FlexDirection.RowReverse }) {
    2.   Text('1').width('33%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('33%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .height(70)
    7. .width('90%')
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • FlexDirection.Column:主轴为垂直方向,子元素从起始端沿着垂直方向开始排布。
    1. Flex({ direction: FlexDirection.Column }) {
    2.   Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .height(70)
    7. .width('90%')
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • FlexDirection.ColumnReverse:主轴为垂直方向,子元素从终点端沿着FlexDirection. Column相反的方向开始排布。
    1. Flex({ direction: FlexDirection.ColumnReverse }) {
    2.   Text('1').width('100%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('100%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('100%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .height(70)
    7. .width('90%')
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

布局换行

弹性布局分为单行布局和多行布局。默认情况下,Flex容器中的子元素都排在一条线(又称“轴线”)上。wrap属性控制当子元素主轴尺寸之和大于容器主轴尺寸时,Flex是单行布局还是多行布局。在多行布局时,通过交错轴方向,确认新行分列方向。


  • FlexWrap. NoWrap(默认值):不换行。如果子元素的宽度总和大于父元素的宽度,则子元素会被压缩宽度。
    1. Flex({ wrap: FlexWrap.NoWrap }) {
    2.   Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .width('90%')
    7. .padding(10)
    8. .backgroundColor(0xAFEEEE)
    复制代码

  • FlexWrap. Wrap:换行,每一行子元素按照主轴方向分列。
    1. Flex({ wrap: FlexWrap.Wrap }) {
    2.   Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('50%').height(50).backgroundColor(0xD2B48C)
    5. }
    6. .width('90%')
    7. .padding(10)
    8. .backgroundColor(0xAFEEEE)
    复制代码

  • FlexWrap. WrapReverse:换行,每一行子元素按照主轴反方向分列。
    1. Flex({ wrap: FlexWrap.WrapReverse}) {
    2.   Text('1').width('50%').height(50).backgroundColor(0xF5DEB3)
    3.   Text('2').width('50%').height(50).backgroundColor(0xD2B48C)
    4.   Text('3').width('50%').height(50).backgroundColor(0xF5DEB3)
    5. }
    6. .width('90%')
    7. .padding(10)
    8. .backgroundColor(0xAFEEEE)
    复制代码

主轴对齐方式(justifyContent)

通过justifyContent参数设置子元素在主轴方向的对齐方式。

交错轴对齐方式(alignItems)

容器和子元素都可以设置交错轴对齐方式,且子元素设置的对齐方式优先级较高。


  • ItemAlign.Auto:利用Flex容器中默认配置
    1. let SWh:Record<string,number|string> = { 'width': '90%', 'height': 80 }
    2. Flex({ alignItems: ItemAlign.Auto }) {  
    3.   Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)  
    4.   Text('2').width('33%').height(40).backgroundColor(0xD2B48C)  
    5.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    6. }
    7. .size(SWh)
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • ItemAlign.Start:交错轴方向首部对齐。
    1. let  SWh:Record<string,number|string> = { 'width':'90%','height':80}
    2. Flex({alignItems: ItemAlign.Start}){
    3.   Text('1').width('33%').height(30).backgorunColor(0xF5DEB3)
    4.   Text('2').width('33%').height(40).backgroundColor(0xD2B48C)  
    5.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    6. }
    7. .size(SWh)
    8. .padding(0)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • ItemAlign.Center:交错轴方向居中对齐。
    1. let SWh:Record<string,number|string> = { 'width': '90%', 'height': 80 }
    2. Flex({ alignItems: ItemAlign.Center }) {  
    3.   Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)  
    4.   Text('2').width('33%').height(40).backgroundColor(0xD2B48C)  
    5.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    6. }
    7. .size(SWh)
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • ItemAlign.End:交错轴方向底部对齐。
    1. let SWh:Record<string,number|string> = { 'width': '90%', 'height': 80 }
    2. Flex({ alignItems: ItemAlign.End }) {  
    3.   Text('1').width('33%').height(30).backgroundColor(0xF5DEB3)  
    4.   Text('2').width('33%').height(40).backgroundColor(0xD2B48C)  
    5.   Text('3').width('33%').height(50).backgroundColor(0xF5DEB3)
    6. }
    7. .size(SWh)
    8. .padding(10)
    9. .backgroundColor(0xAFEEEE)
    复制代码

  • ItemAlign.Stretch:交错轴方向(竖向)拉伸填充,在未设置尺寸时,拉伸到容器尺寸。
  1. let SWh:Record<string,number|string> = { 'width': '90%', 'height': 80 }
  2. Flex({ alignItems: ItemAlign.Stretch }) {  
  3.   Text('1').width('33%').backgroundColor(0xF5DEB3)  
  4.   Text('2').width('33%').backgroundColor(0xD2B48C)  
  5.   Text('3').width('33%').backgroundColor(0xF5DEB3)
  6. }
  7. .size(SWh)
  8. .padding(10)
  9. .backgroundColor(0xAFEEEE)
复制代码



  • 子元素设置交错轴对齐
    子元素的alignSelf属性也可以设置子元素在父容器交错轴的对齐格式,且会覆盖Flex布局容器中alignItems配置。如下例所示:
    1. Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { // 容器组件设置子元素居中
    2.   Text('alignSelf Start').width('25%').height(80)
    3.     .alignSelf(ItemAlign.Start) //子元素设置了交叉轴对齐方式
    4.     .backgroundColor(0xF5DEB3)
    5.   Text('alignSelf Baseline')
    6.     .alignSelf(ItemAlign.Baseline)//子元素设置了交叉轴对齐方式
    7.     .width('25%')
    8.     .height(80)
    9.     .backgroundColor(0xD2B48C)
    10.   Text('alignSelf Baseline').width('25%').height(100)
    11.     .backgroundColor(0xF5DEB3)
    12.     .alignSelf(ItemAlign.Baseline)//子元素设置了交叉轴对齐方式
    13.   Text('no alignSelf').width('25%').height(100)
    14.     .backgroundColor(0xD2B48C)
    15.   Text('no alignSelf').width('25%').height(100)
    16.     .backgroundColor(0xF5DEB3)
    17. }.width('90%').height(220).backgroundColor(0xAFEEEE)
    复制代码

  • 内容对齐(多行Flex生效)
    可以通过alignContent参数设置子元素各行在交错轴剩余空间内的对齐方式,只在多行的Flex布局中生效,可选值有:

    • FlexAlign.Start:子元素各行与交错轴起点对齐
      1. Flex({ justifyContent: FlexAlign.SpaceBetween, wrap: FlexWrap.Wrap, alignContent: FlexAlign.Start }) {
      2.   Text('1').width('30%').height(20).backgroundColor(0xF5DEB3)
      3.   Text('2').width('60%').height(20).backgroundColor(0xD2B48C)
      4.   Text('3').width('40%').height(20).backgroundColor(0xD2B48C)
      5.   Text('4').width('30%').height(20).backgroundColor(0xF5DEB3)
      6.   Text('5').width('20%').height(20).backgroundColor(0xD2B48C)
      7. }
      8. .width('90%')
      9. .height(100)
      10. .backgroundColor(0xAFEEEE)         
      复制代码

    • FlexAlign.Center:子元素各行在交错轴方向居中对齐

    • FlexAlign.End:子元素各行与交错轴终点对齐。

    • FlexAlign.SpaceBetween:子元素各行与交错轴两端对齐,各行间垂直间距平均分布

    • FlexAlign.SpaceAround:子元素各行间距相称,是元素首尾行与交错轴两端距离的两倍。

    • FlexAlign.SpaceEvenly: 子元素各行间距,子元素首尾行与交错轴两端距离都相称。


自适应拉伸



  • flexBasis:设置子元素在父容器主轴方向上的基准尺寸。如果设置了该属性,则子项占用的空间为该属性所设置的值;如果没设置该属性,那子项的空间为width/height的值。
    1. Flex() {
    2.   Text('flexBasis("auto")')
    3.     .flexBasis('auto') // 未设置width以及flexBasis值为auto,内容自身宽度
    4.     .height(100)
    5.     .backgroundColor(0xF5DEB3)
    6.   Text('flexBasis("auto")'+' width("40%")')
    7.     .width('40%')
    8.     .flexBasis('auto') //设置width以及flexBasis值auto,使用width的值
    9.     .height(100)
    10.     .backgroundColor(0xD2B48C)
    11.   Text('flexBasis(100)')  // 未设置width以及flexBasis值为100,宽度为100vp
    12.     .flexBasis(100)  
    13.     .height(100)
    14.     .backgroundColor(0xF5DEB3)
    15.   Text('flexBasis(100)')
    16.     .flexBasis(100)
    17.     .width(200) // flexBasis值为100,覆盖width的设置值,宽度为100vp
    18.     .height(100)
    19.     .backgroundColor(0xD2B48C)
    20. }.width('90%').height(120).padding(10).backgroundColor(0xAFEEEE)
    复制代码

  • flexGrow:设置父容器的剩余空间分配给此属性所在组件的比例。用于分配父组件的剩余空间。
    1. Flex() {
    2.   Text('flexGrow(2)')
    3.     .flexGrow(2)
    4.     .width(100)
    5.     .height(100)
    6.     .backgroundColor(0xF5DEB3)
    7.   Text('flexGrow(3)')
    8.     .flexGrow(3)
    9.     .width(100)
    10.     .height(100)
    11.     .backgroundColor(0xD2B48C)
    12.   Text('no flexGrow')
    13.     .width(100)
    14.     .height(100)
    15.     .backgroundColor(0xF5DEB3)
    16. }.width(420).height(120).padding(10).backgroundColor(0xAFEEEE)
    复制代码

    父容器宽度420vp,三个子元素原始宽度为100vp,左右padding为20vp,总和320vp,剩余空间100vp根据flexGrow值的占比分配给子元素,未设置flexGrow的子元素不参与“瓜分”。
    第一个元素以及第二个元素以2:3分配剩下的100vp。第一个元素为100vp+100vp * 2/5=140vp,第二个元素为100vp+100vp * 3/5=160vp。
  • flexShrink: 当父容器空间不足时,子元素的压缩比例。
    1. Flex({ direction: FlexDirection.Row }) {
    2.   Text('flexShrink(3)')
    3.     .flexShrink(3)
    4.     .width(200)
    5.     .height(100)
    6.     .backgroundColor(0xF5DEB3)
    7.   
    8.   Text('no flexShrink')
    9.     .width(200)
    10.     .height(100)
    11.     .backgroundColor(0xD2B48C)
    12.   Text('flexShrink(2)')
    13.     .flexShrink(2)
    14.     .width(200)
    15.     .height(100)
    16.     .backgroundColor(0xF5DEB3)  
    17. }.width(400).height(120).padding(10).backgroundColor(0xAFEEEE)
    复制代码

4)相对布局 (RelativeContainer)

概述

RelativeContainer为采用相对布局的容器,支持容器内部的子元素设置相对位置关系,适用于界面复杂场景的情况,对多个子组件举行对齐和分列。子元素支持指定兄弟元素作为锚点,也支持指定父容器作为锚点,基于锚点做相对位置布局。下图是一个RelativeContainer的概念图,图中的虚线表示位置的依赖关系。

根本概念



  • 锚点:通过锚点设置当前元素基于哪个元素确定位置。
  • 对齐方式:通过对齐方式,设置当前元素是基于锚点的上中下对齐,还是基于锚点的左中右对齐。
设置依赖关系



  • 锚点设置
    锚点设置是指设置子元素相对于父元素或兄弟元素的位置依赖关系。在水平方向上,可以设置left、middle、right的锚点。在竖直方向上,可以设置top、center、bottom的锚点。
    为了明确定义锚点,必须为RelativeContainer及其子元素设置ID,用于指定锚点信息。ID默认为“container”,其余子元素的ID通过id属性设置。不设置id的组件能显示,但是不能被其他子组件作为锚点,相对布局容器会为其拼接id,此id的规律无法被应用感知。互相依赖,环形依赖时容器内子组件全部不绘制。同方向上两个以上位置设置锚点,但锚点位置逆序时此子组件巨细为0,即不绘制。

    • RelativeContainer父组件为锚点,__container__代表父容器的ID。
      1. let AlignRus: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
      2.   'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
      3.   'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start }
      4. }
      5. let AlignRue: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
      6.   'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
      7.   'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
      8. }
      9. let Mleft: Record<string, number> = { 'left': 20 }
      10. let BWC: Record<string, number | string> = { 'width': 2, 'color': '#6699FF' }
      11. @Entry
      12. @Component
      13. struct Index {
      14.   build() {
      15.     RelativeContainer() {
      16.       Row() {
      17.         Text('row1')
      18.       }
      19.       .justifyContent(FlexAlign.Center)
      20.       .width(100)
      21.       .height(100)
      22.       .backgroundColor('#a3cf62')
      23.       .alignRules(AlignRus)
      24.       .id("row1")
      25.       Row() {
      26.         Text('row2')
      27.       }
      28.       .justifyContent(FlexAlign.Center)
      29.       .width(100)
      30.       .height(100)
      31.       .backgroundColor('#00ae9d')
      32.       .alignRules(AlignRue)
      33.       .id("row2")
      34.     }.width(300).height(300)
      35.     .margin(Mleft)
      36.     .border(BWC)
      37.   }
      38. }
      复制代码

    • 以兄弟元素为锚点。
      1. let AlignRus: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
      2.   'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
      3.   'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start }
      4. }
      5. let RelConB: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
      6.   'top': { 'anchor': 'row1', 'align': VerticalAlign.Bottom },
      7.   'left': { 'anchor': 'row1', 'align': HorizontalAlign.Start }
      8. }
      9. let Mleft: Record<string, number> = { 'left': 20 }
      10. let BWC: Record<string, number | string> = { 'width': 2, 'color': '#6699FF' }
      11. @Entry
      12. @Component
      13. struct Index {
      14.   build() {
      15.     RelativeContainer() {
      16.       Row() {
      17.         Text('row1')
      18.       }
      19.       .justifyContent(FlexAlign.Center)
      20.       .width(100)
      21.       .height(100)
      22.       .backgroundColor('#00ae9d')
      23.       .alignRules(AlignRus)
      24.       .id("row1")
      25.       Row() {
      26.         Text('row2')
      27.       }
      28.       .justifyContent(FlexAlign.Center)
      29.       .width(100)
      30.       .height(100)
      31.       .backgroundColor('#a3cf62')
      32.       .alignRules(RelConB)
      33.       .id("row2")
      34.     }.width(300).height(300)
      35.     .margin(Mleft)
      36.     .border(BWC)
      37.   }
      38. }
      复制代码

    • 子组件锚点可以任意选择,但需注意不要相互依赖。
      1. @Entry
      2. @Component
      3. struct Index {
      4.   build() {
      5.     Row() {
      6.       RelativeContainer() {
      7.         Row(){Text('row1')}.justifyContent(FlexAlign.Center).width(100).height(100)
      8.         .backgroundColor('#a3cf62')
      9.         .alignRules({
      10.           top: {anchor: "__container__", align: VerticalAlign.Top},
      11.           left: {anchor: "__container__", align: HorizontalAlign.Start}
      12.         })
      13.         .id("row1")
      14.         Row(){Text('row2')}.justifyContent(FlexAlign.Center).width(100)
      15.         .backgroundColor('#00ae9d')
      16.         .alignRules({
      17.           top: {anchor: "__container__", align: VerticalAlign.Top},
      18.           right: {anchor: "__container__", align: HorizontalAlign.End},
      19.           bottom: {anchor: "row1", align: VerticalAlign.Center},
      20.         })
      21.         .id("row2")
      22.         Row(){Text('row3')}.justifyContent(FlexAlign.Center).height(100)
      23.         .backgroundColor('#0a59f7')
      24.         .alignRules({
      25.           top: {anchor: "row1", align: VerticalAlign.Bottom},
      26.           left: {anchor: "row1", align: HorizontalAlign.Start},
      27.           right: {anchor: "row2", align: HorizontalAlign.Start}
      28.         })
      29.         .id("row3")
      30.         Row(){Text('row4')}.justifyContent(FlexAlign.Center)
      31.         .backgroundColor('#2ca9e0')
      32.         .alignRules({
      33.           top: {anchor: "row3", align: VerticalAlign.Bottom},
      34.           left: {anchor: "row1", align: HorizontalAlign.Center},
      35.           right: {anchor: "row2", align: HorizontalAlign.End},
      36.           bottom: {anchor: "__container__", align: VerticalAlign.Bottom}
      37.         })
      38.         .id("row4")
      39.       }
      40.       .width(300).height(300)
      41.       .margin({left: 50})
      42.       .border({width:2, color: "#6699FF"})
      43.     }
      44.     .height('100%')
      45.   }
      46. }
      复制代码


  • 设置相对于锚点的对其位置
    设置了锚点之后,可以通过align设置相对于锚点的对齐位置。
    在水平方向上,对齐位置可以设置为HorizontalAlign.Start、HorizontalAlign.Center、HorizontalAlign.End

    在竖直方向上,对齐位置可以设置为VerticalAlign.Top、VerticalAlign.Center、VerticalAlign.Bottom。

  • 子组件位置偏移
    子组件颠末相对位置对齐后,位置大概还不是目标位置,开辟者可根据必要举行额外偏移设置offset
    1. @Entry
    2. @Component
    3. struct Index {
    4. build() {
    5.   Row() {
    6.     RelativeContainer() {
    7.       Row() {
    8.         Text('row1')
    9.       }
    10.       .justifyContent(FlexAlign.Center)
    11.       .width(100)
    12.       .height(100)
    13.       .backgroundColor('#a3cf62')
    14.       .alignRules({
    15.         top: { anchor: "__container__", align: VerticalAlign.Top },
    16.         left: { anchor: "__container__", align: HorizontalAlign.Start }
    17.       })
    18.       .id("row1")
    19.       Row() {
    20.         Text('row2')
    21.       }
    22.       .justifyContent(FlexAlign.Center)
    23.       .width(100)
    24.       .backgroundColor('#00ae9d')
    25.       .alignRules({
    26.         top: { anchor: "__container__", align: VerticalAlign.Top },
    27.         right: { anchor: "__container__", align: HorizontalAlign.End },
    28.         bottom: { anchor: "row1", align: VerticalAlign.Center },
    29.       })
    30.       .offset({
    31.         x: -40,
    32.         y: -20
    33.       })
    34.       .id("row2")
    35.       Row() {
    36.         Text('row3')
    37.       }
    38.       .justifyContent(FlexAlign.Center)
    39.       .height(100)
    40.       .backgroundColor('#0a59f7')
    41.       .alignRules({
    42.         top: { anchor: "row1", align: VerticalAlign.Bottom },
    43.         left: { anchor: "row1", align: HorizontalAlign.End },
    44.         right: { anchor: "row2", align: HorizontalAlign.Start }
    45.       })
    46.       .offset({
    47.         x: -10,
    48.         y: -20
    49.       })
    50.       .id("row3")
    51.       Row() {
    52.         Text('row4')
    53.       }
    54.       .justifyContent(FlexAlign.Center)
    55.       .backgroundColor('#2ca9e0')
    56.       .alignRules({
    57.         top: { anchor: "row3", align: VerticalAlign.Bottom },
    58.         bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
    59.         left: { anchor: "__container__", align: HorizontalAlign.Start },
    60.         right: { anchor: "row1", align: HorizontalAlign.End }
    61.       })
    62.       .offset({
    63.         x: -10,
    64.         y: -30
    65.       })
    66.       .id("row4")
    67.       Row() {
    68.         Text('row5')
    69.       }
    70.       .justifyContent(FlexAlign.Center)
    71.       .backgroundColor('#30c9f7')
    72.       .alignRules({
    73.         top: { anchor: "row3", align: VerticalAlign.Bottom },
    74.         bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
    75.         left: { anchor: "row2", align: HorizontalAlign.Start },
    76.         right: { anchor: "row2", align: HorizontalAlign.End }
    77.       })
    78.       .offset({
    79.         x: 10,
    80.         y: 20
    81.       })
    82.       .id("row5")
    83.       Row() {
    84.         Text('row6')
    85.       }
    86.       .justifyContent(FlexAlign.Center)
    87.       .backgroundColor('#ff33ffb5')
    88.       .alignRules({
    89.         top: { anchor: "row3", align: VerticalAlign.Bottom },
    90.         bottom: { anchor: "row4", align: VerticalAlign.Bottom },
    91.         left: { anchor: "row3", align: HorizontalAlign.Start },
    92.         right: { anchor: "row3", align: HorizontalAlign.End }
    93.       })
    94.       .offset({
    95.         x: -15,
    96.         y: 10
    97.       })
    98.       .backgroundImagePosition(Alignment.Bottom)
    99.       .backgroundImageSize(ImageSize.Cover)
    100.       .id("row6")
    101.     }
    102.     .width(300).height(300)
    103.     .margin({ left: 50 })
    104.     .border({ width: 2, color: "#6699FF" })
    105.   }
    106.   .height('100%')
    107. }
    108. }
    复制代码

多种组件的对其布局

Row、Column、Flex、Stack等多种布局组件,可按照RelativeContainer组件规则举行对齐排布。
  1. @Entry
  2. @Component
  3. struct Index {
  4. @State value: number = 0
  5. build() {
  6.   Row() {
  7.     RelativeContainer() {
  8.       Row()
  9.         .width(100)
  10.         .height(100)
  11.         .backgroundColor('#a3cf62')
  12.         .alignRules({
  13.           top: { anchor: "__container__", align: VerticalAlign.Top },
  14.           left: { anchor: "__container__", align: HorizontalAlign.Start }
  15.         })
  16.         .id("row1")
  17.       Column()
  18.         .width('50%')
  19.         .height(30)
  20.         .backgroundColor('#00ae9d')
  21.         .alignRules({
  22.           top: { anchor: "__container__", align: VerticalAlign.Top },
  23.           left: { anchor: "__container__", align: HorizontalAlign.Center }
  24.         })
  25.         .id("row2")
  26.       Flex({ direction: FlexDirection.Row }) {
  27.         Text('1').width('20%').height(50).backgroundColor('#0a59f7')
  28.         Text('2').width('20%').height(50).backgroundColor('#2ca9e0')
  29.         Text('3').width('20%').height(50).backgroundColor('#0a59f7')
  30.         Text('4').width('20%').height(50).backgroundColor('#2ca9e0')
  31.       }
  32.       .padding(10)
  33.       .backgroundColor('#30c9f7')
  34.       .alignRules({
  35.         top: { anchor: "row2", align: VerticalAlign.Bottom },
  36.         left: { anchor: "__container__", align: HorizontalAlign.Start },
  37.         bottom: { anchor: "__container__", align: VerticalAlign.Center },
  38.         right: { anchor: "row2", align: HorizontalAlign.Center }
  39.       })
  40.       .id("row3")
  41.       Stack({ alignContent: Alignment.Bottom }) {
  42.         Text('First child, show in bottom').width('90%').height('100%').backgroundColor('#a3cf62').align(Alignment.Top)
  43.         Text('Second child, show in top').width('70%').height('60%').backgroundColor('#00ae9d').align(Alignment.Top)
  44.       }
  45.       .margin({ top: 5 })
  46.       .alignRules({
  47.         top: { anchor: "row3", align: VerticalAlign.Bottom },
  48.         left: { anchor: "__container__", align: HorizontalAlign.Start },
  49.         bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  50.         right: { anchor: "row3", align: HorizontalAlign.End }
  51.       })
  52.       .id("row4")
  53.     }
  54.     .width(300).height(300)
  55.     .margin({ left: 50 })
  56.     .border({ width: 2, color: "#6699FF" })
  57.   }
  58.   .height('100%')
  59. }
  60. }
复制代码

组件尺寸

子组件尺寸巨细不会受到相对布局规则的影响。若子组件某个方向上设置两个或以上alignRules时最好不设置此方向尺寸巨细,否则对齐规则确定的组件尺寸与开辟者设置的尺寸大概产生冲突。
5)栅格布局 (GridRow/GridCol)

概述

栅格布局是一种通用的辅助定位工具,对移动设备的界面设计有较好的借鉴作用。主要优势包罗:
提供可循的规律:栅格布局可以为布局提供规律性的布局,办理多尺寸多设备的动态布局题目。通过将页面划分为等宽的列数和行数,可以方便地对页面元素举行定位和排版。

  • 同一的定位标注:栅格布局可以为系统提供一种同一的定位标注,保证不同设备上各个模块的布局同等性。这可以淘汰设计和开辟的复杂度,进步工作效率。
  • 机动的间距调整方法:栅格布局可以提供一种机动的间距调整方法,满足特殊场景布局调整的需求。通过调整列与列之间和行与行之间的间距,可以控制整个页面的排版效果。
  • 主动换行和自适应:栅格布局可以完成一对多布局的主动换行和自适应。当页面元素的数量超出了一行或一列的容量时,他们会主动换到下一行或下一列,而且在不同的设备上自适应排版,使得页面布局更加机动和适应性强。
栅格容器GridRow

1)栅格系统断点
栅格系统以设备的水平宽度(屏幕密度像素值,单元vp)作为断点依据,定义设备的宽度类型,形成了一套断点规则。开辟者可根据需求在不同的断点区间实现不同的页面布局效果。
栅格系统默认断点将设备宽度分为xs、sm、md、lg四类,尺寸范围如下:
断点名称取值范围(vp)设备描述xs[0,320]最小宽度类型设备sm[320,520]小宽度类型设备md[520,840]中等宽度类型设备lg[840,+]大宽度类型设备

  • 针对断点位置,开辟者根据实际利用场景,通过一个单调递增数组设置。由于breakpoints最多支持六个断点,单调递增数组长度最大为5
    1. breakpoints: {value: ['100vp', '200vp']}
    复制代码
    表示启用xs、sm、md共3个断点,小于100vp为xs,100vp-200vp为sm,大于200vp为md。
    1. breakpoints: {value: ['320vp', '520vp', '840vp', '1080vp']}
    复制代码
    表示启用xs、sm、md、lg、xl共5个断点,小于320vp为xs,320vp-520vp为sm,520vp-840vp为md,840vp-1080vp为lg,大于1080vp为xl。
  • 栅格系统通过监听窗口或容器的尺寸变化举行断点,通过reference设置断点切换参考物。 考虑到应用大概以非全屏窗口的形式显示,以应用窗口宽度为参照物更为通用
比方,利用栅格的默认列数12列,通过断点设置将应用宽度分成六个区间,在各区间中,每个栅格子元素占用的列数均不同:
  1. @State bgColors: ResourceColor[] =
  2.     ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
  3.       'rgb(255,192,0)', 'rgb(170,10,33)'];
  4. ...
  5. GridRow({
  6.   breakpoints: {
  7.     value: ['200vp', '300vp', '400vp', '500vp', '600vp'],
  8.     reference: BreakpointsReference.WindowSize
  9.   }
  10. }) {
  11.    ForEach(this.bgColors, (color:ResourceColor, index?:number|undefined) => {
  12.      GridCol({
  13.        span: {
  14.          xs: 2, // 在最小宽度类型设备上,栅格子组件占据的栅格容器2列。
  15.          sm: 3, // 在小宽度类型设备上,栅格子组件占据的栅格容器3列。
  16.          md: 4, // 在中等宽度类型设备上,栅格子组件占据的栅格容器4列。
  17.          lg: 6, // 在大宽度类型设备上,栅格子组件占据的栅格容器6列。
  18.          xl: 8, // 在特大宽度类型设备上,栅格子组件占据的栅格容器8列。
  19.          xxl: 12 // 在超大宽度类型设备上,栅格子组件占据的栅格容器12列。
  20.        }
  21.      }) {
  22.        Row() {
  23.          Text(`${index}`)
  24.        }.width("100%").height('50vp')
  25.      }.backgroundColor(color)
  26.    })
  27. }                                                                    
复制代码

2)布局的总列数
GridRow中通过columns设置栅格布局的总列数。


  • columns默认值为12,即在未设置columns时,任何断点下,栅格布局被分成12列。
    1. @State bgColors: ResourceColor[] =
    2.   ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    3.     'rgb(255,192,0)', 'rgb(170,10,33)', 'rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)'];
    4. ...
    5. GridRow() {
    6.   ForEach(this.bgColors, (item:ResourceColor, index?:number|undefined) => {
    7.     GridCol() {
    8.       Row() {
    9.           Text(`${index}`)
    10.       }.width('100%').height('50')
    11.     }.backgroundColor(item)
    12.   })
    13. }           
    复制代码

    当columns为自定义值,栅格布局在任何尺寸设备下都被分为columns列。下面分别设置栅格布局列数为4和8,子元素默认占一列,效果如下:
    1. class CurrTmp{
    2.   currentBp: string = 'unknown';
    3.   set(val:string){
    4.     this.currentBp = val
    5.   }
    6. }
    7. let BorderWH:Record<string,Color|number> = { 'color': Color.Blue, 'width': 2 }
    8. @State bgColors: ResourceColor[] =
    9.     ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    10.       'rgb(255,192,0)', 'rgb(170,10,33)'];
    11. @State currentBp: string = 'unknown';
    12. ...
    13. Row() {
    14.   GridRow({ columns: 4 }) {
    15.     ForEach(this.bgColors, (item: ResourceColor, index?:number|undefined) => {
    16.       GridCol() {
    17.         Row() {
    18.           Text(`${index}`)
    19.         }.width('100%').height('50')
    20.       }.backgroundColor(item)
    21.     })
    22.   }
    23.   .width('100%').height('100%')
    24.   .onBreakpointChange((breakpoint:string) => {
    25.     let CurrSet:CurrTmp = new CurrTmp()
    26.     CurrSet.set(breakpoint)
    27.   })
    28. }
    29. .height(160)
    30. .border(BorderWH)
    31. .width('90%')
    32. Row() {
    33.   GridRow({ columns: 8 }) {
    34.     ForEach(this.bgColors, (item: ResourceColor, index?:number|undefined) => {
    35.         GridCol() {
    36.           Row() {
    37.             Text(`${index}`)
    38.           }.width('100%').height('50')
    39.         }.backgroundColor(item)
    40.     })
    41.   }
    42.   .width('100%').height('100%')
    43.   .onBreakpointChange((breakpoint:string) => {
    44.     let CurrSet:CurrTmp = new CurrTmp()
    45.     CurrSet.set(breakpoint)
    46.   })
    47. }
    48. .height(160)
    49. .border(BorderWH)
    50. .width('90%')
    复制代码

3)分列方向
栅格布局中,可以通过设置GridRow的direction属性来指定栅格子组件在栅格容器中的分列方向。该属性可以设置为GridRowDirection.Row(从左往右分列)或GridRowDirection.RowReverse(从右往左分列),以满足不同的布局需求。通过公道的direction属性设置,可以使得页面布局更加机动和符合设计要求。


  • 子组件默认从左往右分列。
    1. GridRow({ direction: GridRowDirection.Row }){}
    复制代码

  • 子组件从右往左分列。
    1. GridRow({ direction: GridRowDirection.RowReverse }){}
    复制代码

4)子组件隔断
GridRow中通过gutter属性设置子元素在水平和垂直方向的间距。
  1. GridRow({ gutter: 10 }){}
复制代码



  • 当gutter类型为GutterOption时,单独设置栅格子组件水平垂直边距,x属性为水平方向间距,y为垂直方向间距。
    1. GridRow({ gutter: { x: 20, y: 50 } }){}
    复制代码

子组件GridCol

GridCol组件作为GridRow组件的子组件,通过给GridCol传参大概设置属性两种方式,设置span(占用列数),offset(偏移列数),order(元素序号)的值。


  • 设置span
    1. let Gspan:Record<string,number> = { 'xs': 1, 'sm': 2, 'md': 3, 'lg': 4 }
    2. GridCol({ span: 2 }){}
    3. GridCol({ span: { xs: 1, sm: 2, md: 3, lg: 4 } }){}
    4. GridCol(){}.span(2)
    5. GridCol(){}.span(Gspan)
    复制代码
  • 设置offset
    1. let Goffset:Record<string,number> = { 'xs': 1, 'sm': 2, 'md': 3, 'lg': 4 }
    2. GridCol({ offset: 2 }){}
    3. GridCol({ offset: { xs: 2, sm: 2, md: 2, lg: 2 } }){}
    4. GridCol(){}.offset(Goffset)
    复制代码
  • 设置order
    1. let Gorder:Record<string,number> = { 'xs': 1, 'sm': 2, 'md': 3, 'lg': 4 }
    2. GridCol({ order: 2 }){}
    3. GridCol({ order: { xs: 1, sm: 2, md: 3, lg: 4 } }){}
    4. GridCol(){}.order(2)
    5. GridCol(){}.order(Gorder)
    复制代码
1)span
子组件占栅格布局的列数,决定了子组件的宽度,默认为1。


  • 当类型为number时,子组件在所有尺寸设备下占用的列数相同。
    1. @State bgColors: ResourceColor[] =
    2.   ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    3.     'rgb(255,192,0)', 'rgb(170,10,33)'];
    4. ...
    5. GridRow({ columns: 8 }) {
    6.   ForEach(this.bgColors, (color:ResourceColor, index?:number|undefined) => {
    7.     GridCol({ span: 2 }) {      
    8.       Row() {
    9.         Text(`${index}`)
    10.       }.width('100%').height('50vp')         
    11.     }
    12.     .backgroundColor(color)
    13.   })
    14. }               
    复制代码

  • 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件所占列数设置,各个尺寸下数值可不同。
    1. @State bgColors: ResourceColor[] =
    2.   ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    3.     'rgb(255,192,0)', 'rgb(170,10,33)'];
    4. ...
    5. GridRow({ columns: 8 }) {
    6.   ForEach(this.bgColors, (color:ResourceColor, index?:number|undefined) => {
    7.     GridCol({ span: { xs: 1, sm: 2, md: 3, lg: 4 } }) {      
    8.       Row() {
    9.         Text(`${index}`)
    10.       }.width('100%').height('50vp')         
    11.     }
    12.     .backgroundColor(color)
    13.   })
    14. }               
    复制代码

2)offset
栅格子组件相对于前一个子组件的偏移列数,默认为0。


  • 当类型为number时,子组件偏移相同列数。
    1. @State bgColors: ResourceColor[] =
    2.   ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    3.     'rgb(255,192,0)', 'rgb(170,10,33)'];
    4. ...
    5. GridRow() {
    6.   ForEach(this.bgColors, (color:ResourceColor, index?:number|undefined) => {
    7.     GridCol({ offset: 2 }) {      
    8.       Row() {
    9.         Text('' + index)
    10.       }.width('100%').height('50vp')         
    11.     }
    12.     .backgroundColor(color)
    13.   })
    14. }               
    复制代码

    栅格默认分成12列,每一个子组件默认占1列,偏移2列,每个子组件及间距共占3列,一行放四个子组件。
  • 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件所占列数设置,各个尺寸下数值可不同。
    1. @State bgColors: ResourceColor[] =
    2.   ['rgb(213,213,213)', 'rgb(150,150,150)', 'rgb(0,74,175)', 'rgb(39,135,217)', 'rgb(61,157,180)', 'rgb(23,169,141)',
    3.     'rgb(255,192,0)', 'rgb(170,10,33)'];
    4. ...
    5. GridRow() {
    6.   ForEach(this.bgColors, (color:ResourceColor, index?:number|undefined) => {
    7.     GridCol({ offset: { xs: 1, sm: 2, md: 3, lg: 4 } }) {      
    8.       Row() {
    9.         Text('' + index)
    10.       }.width('100%').height('50vp')         
    11.     }
    12.     .backgroundColor(color)
    13.   })
    14. }                 
    复制代码

3)order
栅格子组件的序号,决定子组件分列次序。当子组件不设置order大概设置相同的order, 子组件按照代码顺序展示。当子组件设置不同的order时,order较小的组件在前,较大的在后。
当子组件部分设置order,部分不设置order时,未设置order的子组件依次排序靠前,设置了order的子组件按照数值从小到大分列。


  • 当类型为number时,子组件在任何尺寸下排序次序同等。
    1. GridRow() {
    2. GridCol({ order: 4 }) {
    3.   Row() {
    4.     Text('1')
    5.   }.width('100%').height('50vp')
    6. }.backgroundColor('rgb(213,213,213)')
    7. GridCol({ order: 3 }) {
    8.   Row() {
    9.     Text('2')
    10.   }.width('100%').height('50vp')
    11. }.backgroundColor('rgb(150,150,150)')
    12. GridCol({ order: 2 }) {
    13.   Row() {
    14.     Text('3')
    15.   }.width('100%').height('50vp')
    16. }.backgroundColor('rgb(0,74,175)')
    17. GridCol({ order: 1 }) {
    18.   Row() {
    19.     Text('4')
    20.   }.width('100%').height('50vp')
    21. }.backgroundColor('rgb(39,135,217)')
    22. }
    复制代码

  • 当类型为GridColColumnOption时,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备中子组件排序次序设置。在xs设备中,子组件分列顺序为1234;sm为2341,md为3412,lg为2431。
    1. GridRow() {
    2.   GridCol({ order: { xs:1, sm:5, md:3, lg:7}}) {
    3.     Row() {
    4.       Text('1')
    5.     }.width('100%').height('50vp')
    6.   }.backgroundColor(Color.Red)
    7.   GridCol({ order: { xs:2, sm:2, md:6, lg:1} }) {
    8.     Row() {
    9.       Text('2')
    10.     }.width('100%').height('50vp')
    11.   }.backgroundColor(Color.Orange)
    12.   GridCol({ order: { xs:3, sm:3, md:1, lg:6} }) {
    13.     Row() {
    14.       Text('3')
    15.     }.width('100%').height('50vp')
    16.   }.backgroundColor(Color.Yellow)
    17.   GridCol({ order: { xs:4, sm:4, md:2, lg:5} }) {
    18.     Row() {
    19.       Text('4')
    20.     }.width('100%').height('50vp')
    21.   }.backgroundColor(Color.Green)
    22. }
    复制代码

栅格组件的嵌套利用

栅格组件也可以嵌套利用,完成一些复杂的布局。
以下示例中,栅格把整个空间分为12份。第一层GridRow嵌套GridCol,分为中心大区域以及“footer”区域。第二层GridRow嵌套GridCol,分为“left”和“right”区域。子组件空间按照上一层父组件的空间划分,粉色的区域是屏幕空间的12列,绿色和蓝色的区域是父组件GridCol的12列,依次举行空间的划分。
  1. @Entry
  2. @Component
  3. struct GridRowExample {
  4.   build() {
  5.     GridRow() {
  6.       GridCol({ span: { sm: 12 } }) {
  7.         GridRow() {
  8.           GridCol({ span: { sm: 2 } }) {
  9.             Row() {
  10.               Text('left').fontSize(24)
  11.             }
  12.             .justifyContent(FlexAlign.Center)
  13.             .height('90%')
  14.           }.backgroundColor('#ff41dbaa')
  15.           GridCol({ span: { sm: 10 } }) {
  16.             Row() {
  17.               Text('right').fontSize(24)
  18.             }
  19.             .justifyContent(FlexAlign.Center)
  20.             .height('90%')
  21.           }.backgroundColor('#ff4168db')
  22.         }
  23.         .backgroundColor('#19000000')
  24.       }
  25.       GridCol({ span: { sm: 12 } }) {
  26.         Row() {
  27.           Text('footer').width('100%').textAlign(TextAlign.Center)
  28.         }.width('100%').height('10%').backgroundColor(Color.Pink)
  29.       }
  30.     }.width('100%').height(300)
  31.   }
  32. }
复制代码

6)媒体查询 (@ohos.mediaquery)

概述


  • 针对设备和应用的属性信息(比如显示区域、深浅色、分辨率),设计出相匹配的布局。
  • 当屏幕发生动态改变时(比如分屏、横竖屏切换),同步更新应用的页面布局。
引入和利用流程

媒体查询通过mediaquery模块接口,设置查询条件并绑定回调函数,任一媒体特性改变时,均会触发回调函数,返回匹配结果,根据返回值更改页面布局大概实现业务逻辑,实现页面的相应式设计。具体步调如下:
1)起首导入媒体查询模块:
  1. import { mediaquery } from '@kit.ArkUI';
复制代码
2)通过matchMediaSync接口设置媒体查询条件,保存返回的条件监听句柄listener。比方监听横屏事件:
  1. let listener: mediaquery.MediaQueryListener = this.getUIContext().getMediaQuery().matchMediaSync('(orientation: landscape)');
复制代码
3)给条件监听句柄listener绑定回调函数onPortrait,当listener检测设备状态变化时实行回调函数。在回调函数内,根据不同设备状态更改页面布局大概实现业务逻辑。
  1. onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) {
  2.   if (mediaQueryResult.matches as boolean) {
  3.     // do something here
  4.   } else {
  5.     // do something here
  6.   }
  7. }
  8. listener.on('change', onPortrait);
复制代码
媒体查询条件

媒体查询条件由媒体类型、逻辑操作符、媒体特性组成,其中媒体类型可省略,逻辑操作符用于连接不同媒体类型与媒体特性,其中,媒体特性要利用“()”包裹且可以有多个。
7)创建列表 (List)

概述

列表是一种复杂的容器,当列表项达到肯定命量,内容超过屏幕巨细时,可以主动提供滚动功能。它得当用于呈现同类数据类型或数据类型集,比方图片和文本。在列表中显示数据集合是很多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
利用列表可以轻松高效地显示布局化、可滚动的信息。通过在List组件中按垂直大概水平方向线性分列子组件ListItemGroupListItem,为列表中的行或列提供单个视图,或利用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach布局,构建一个列表。List组件支持利用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。
布局与约束

列表作为一种容器,会主动按其滚动方向分列子组件,向列表中添加组件或从列表中移除组件会重新分列子组件。
如下图所示,在垂直列表中,List按垂直方向主动分列ListItemGroup或ListItem。
ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。ListItem表示单个列表项,可以包含单个子组件。

1)布局
List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应延伸能力之外,还提供了自适应交错轴方向上分列个数的布局能力。
利用垂直布局能力可以构建单列大概多列垂直滚动列表,如下图所示。

利用水平布局能力可以是构建单行或多行水平滚动列表,如下图所示。

2)约束
列表的主轴方向是指子组件列的分列方向,也是列表的滚动方向。垂直于主轴的轴称为交错轴,其方向与主轴方向相互垂直。
如下图所示,垂直列表的主轴是垂直方向,交错轴是水平方向;水平列表的主轴是水平方向,交错轴是垂直方向。

如下图所示,一个垂直列表B没有设置高度时,其父组件A高度为200vp,若其所有子组件C的高度总和为150vp,则此时列表B的高度为150vp。

如下图所示,同样是没有设置高度的垂直列表B,其父组件A高度为200vp,若其所有子组件C的高度总和为300vp,则此时列表B的高度为200vp。

开辟布局

1)设置主轴方向
List组件主轴默认是垂直方向,即默认情况下不必要手动设置List方向,就可以构建一个垂直滚动列表。
若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。
  1. List() {
  2.   // ...
  3. }
  4. .listDirection(Axis.Horizontal)
复制代码
2)设置交错轴布局
List组件的交错轴布局可以通过lanesalignListItem属性举行设置。
lanes属性用于确定交错轴分列的列表项数量;
alignListItem用于设置子组件在交错轴方向的对齐方式。


  • lanes
    lanes属性的取值类型是 “number | LengthConstrain” ,即整数大概LengthConstrain类型
    lanes的默认值为1,即默认情况下,垂直列表的列数是1。
    1. List() {
    2.   // ...
    3. }
    4. .lanes(2)
    复制代码
    当其取值为LengthConstrain类型时,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数。
    1. @Entry
    2. @Component
    3. struct EgLanes {
    4.   @State egLanes: LengthConstrain = { minLength: 200, maxLength: 300 }
    5.   build() {
    6.     List() {
    7.       // ...
    8.     }
    9.     .lanes(this.egLanes)
    10.   }
    11. }
    复制代码
    比方,假设在垂直列表中设置了lanes的值为{ minLength: 200, maxLength: 300 }。此时,

    • 当List组件宽度为300vp时,由于minLength为200vp,此时列表为一列。
    • 当List组件宽度变化至400vp时,符合两倍的minLength,则此时列表自适应为两列。

  • alignListItem
    同样以垂直列表为例,当alignListItem属性设置为ListItemAlign.Center表示列表项在水平方向上居中对齐。alignListItem的默认值是ListItemAlign.Start,即列表项在列表交错轴方向上默认按首部对齐。
    1. List() {
    2.   // ...
    3. }
    4. .alignListItem(ListItemAlign.Center)
    复制代码
在列表中显示数据

列表视图垂直或水平显示项目集合,在行或列超出屏幕时提供滚动功能,使其得当显示大型数据集合。在最简单的列表形式中,List静态地创建其列表项ListItem的内容。
  1. @Entry
  2. @Component
  3. struct CityList {
  4.   build() {
  5.     List() {
  6.       ListItem() {
  7.         Text('北京').fontSize(24)
  8.       }
  9.       ListItem() {
  10.         Text('杭州').fontSize(24)
  11.       }
  12.       ListItem() {
  13.         Text('上海').fontSize(24)
  14.       }
  15.     }
  16.     .backgroundColor('#FFF1F3F5')
  17.     .alignListItem(ListItemAlign.Center)
  18.   }
  19. }
复制代码

由于在ListItem中只能有一个根节点组件,不支持以平铺形式利用多个组件。因此,若列表项是由多个组件元素组成的,则必要将这多个元素组合到一个容器组件内或组成一个自定义组件。
接洽人列表的列表项中,每个接洽人都有头像和名称。此时,必要将Image和Text封装到一个Row容器内。
  1. List() {
  2.   ListItem() {
  3.     Row() {
  4.       Image($r('app.media.iconE'))
  5.         .width(40)
  6.         .height(40)
  7.         .margin(10)
  8.       Text('小明')
  9.         .fontSize(20)
  10.     }
  11.   }
  12.   ListItem() {
  13.     Row() {
  14.       Image($r('app.media.iconF'))
  15.         .width(40)
  16.         .height(40)
  17.         .margin(10)
  18.       Text('小红')
  19.         .fontSize(20)
  20.     }
  21.   }
  22. }
复制代码

迭代列表内容

应用通过数据集合动态地创建列表。利用循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,降低代码复杂度。 **比方:** ArkTS通过ForEach提供了组件的循环渲染能力。以简单形式的接洽人列表为例,将接洽人名称和头像数据以Contact类布局存储到contacts数组,利用ForEach中嵌套ListItem的形式来代替多个平铺的、内容相似的ListItem,从而淘汰重复代码。
  1. import { util } from '@kit.ArkTS'
  2. class Contact {
  3.   key: string = util.generateRandomUUID(true);
  4.   name: string;
  5.   icon: Resource;
  6.   constructor(name: string, icon: Resource) {
  7.     this.name = name;
  8.     this.icon = icon;
  9.   }
  10. }
  11. @Entry
  12. @Component
  13. struct SimpleContacts {
  14.   private contacts: Array<object> = [
  15.     new Contact('小明', $r("app.media.iconA")),
  16.     new Contact('小红', $r("app.media.iconB")),
  17.   ]
  18.   build() {
  19.     List() {
  20.       ForEach(this.contacts, (item: Contact) => {
  21.         ListItem() {
  22.           Row() {
  23.             Image(item.icon)
  24.               .width(40)
  25.               .height(40)
  26.               .margin(10)
  27.             Text(item.name).fontSize(20)
  28.           }
  29.           .width('100%')
  30.           .justifyContent(FlexAlign.Start)
  31.         }
  32.       }, (item: Contact) => JSON.stringify(item))
  33.     }
  34.     .width('100%')
  35.   }
  36. }
复制代码
在List组件中,ForEach除了可以用来循环渲染ListItem,也可以用来循环渲染ListItemGroup。ListItemGroup的循环渲染详细利用请参见支持分组列表。
自定义列表样式

1)设置内容间距
在初始化列表时,如需在列表项之间添加间距,可以利用space参数。比方,在每个列表项之间沿主轴方向添加10vp的间距:
  1. List({ space: 10 }) {
  2.   // ...
  3. }
复制代码
2)添加分割线
分隔线用来将界面元素隔开,使单个元素更加轻易辨认。如下图所示,当列表项左边有图标(如蓝牙图标),由于图标本身就能很好的区分,此时分隔线从图标之后开始显示即可。

List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。
startMarginendMargin属性分别用于设置分隔线距离列表侧边起始端的距离和距离列表侧边竣事端的距离。
  1. class DividerTmp {
  2.   strokeWidth: Length = 1
  3.   startMargin: Length = 60
  4.   endMargin: Length = 10
  5.   color: ResourceColor = '#ffe9f0f0'
  6.   constructor(strokeWidth: Length, startMargin: Length, endMargin: Length, color: ResourceColor) {
  7.     this.strokeWidth = strokeWidth
  8.     this.startMargin = startMargin
  9.     this.endMargin = endMargin
  10.     this.color = color
  11.   }
  12. }
  13. @Entry
  14. @Component
  15. struct EgDivider {
  16.   @State egDivider: DividerTmp = new DividerTmp(1, 60, 10, '#ffe9f0f0')
  17.   build() {
  18.     List() {
  19.       // ...
  20.     }
  21.     .divider(this.egDivider)
  22.   }
  23. }
复制代码
此示例表示从距离列表侧边起始端60vp开始到距离竣事端10vp的位置,画一条粗细为1vp的分割线,可以实现上图设置列表分隔线的样式。
3)添加滚动条
当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条,如下图所示。

在利用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为BarState,当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不举行任何操作,2秒后滚动条主动消散。
4)支持分组列表
在列表中支持数据的分组展示,可以使列表显示布局清晰,查找方便,从而进步利用效率。分组列表在实际应用中十分常见,如下图所示接洽人列表。

在List组件中可以直接利用一个大概多个ListItemGroup组件,ListItemGroup的宽度默认充满List组件。在初始化ListItemGroup时,可通过header参数设置列表分组的头部组件。
  1. @Entry
  2. @Component
  3. struct ContactsList {
  4.   
  5.   @Builder itemHead(text: string) {
  6.     // 列表分组的头部组件,对应联系人分组A、B等位置的组件
  7.     Text(text)
  8.       .fontSize(20)
  9.       .backgroundColor('#fff1f3f5')
  10.       .width('100%')
  11.       .padding(5)
  12.   }
  13.   build() {
  14.     List() {
  15.       ListItemGroup({ header: this.itemHead('A') }) {
  16.         // 循环渲染分组A的ListItem
  17.       }
  18.       ListItemGroup({ header: this.itemHead('B') }) {
  19.         // 循环渲染分组B的ListItem
  20.       }
  21.     }
  22.   }
  23. }
复制代码
如果多个ListItemGroup布局类似,可以将多个分组的数据组成数组,然后利用ForEach对多个分组举行循环渲染。比方在接洽人列表中,将每个分组的接洽人数据contacts(可参考迭代列表内容章节)和对应分组的标题title数据举行组合,定义为数组contactsGroups。然后在ForEach中对contactsGroups举行循环渲染,即可实现多个分组的接洽人列表。可参考添加粘性标题章节示例代码。
5)添加粘性标题
粘性标题是一种常见的标题模式,常用于定位字母列表的头部元素。如下图所示,在接洽人列表中滚动A部分时,B部分开始的头部元素始终处于A的下方。而在开始滚动B部分时,B的头部会固定在屏幕顶部,直到所有B的项均完成滚动后,才被后面的头部替代。
粘性标题不仅有助于阐明列表中数据的表示形式和用途,还可以帮助用户在大量信息中举行数据定位,从而避免用户在标题所在的表的顶部与感兴趣区域之间反复滚动。

List组件的sticky属性配合ListItemGroup组件利用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果大概尾部组件是否呈现吸底效果。
通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果必要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer。
  1. import { util } from '@kit.ArkTS'
  2. class Contact {
  3.   key: string = util.generateRandomUUID(true);
  4.   name: string;
  5.   icon: Resource;
  6.   constructor(name: string, icon: Resource) {
  7.     this.name = name;
  8.     this.icon = icon;
  9.   }
  10. }
  11. class ContactsGroup {
  12.   title: string = ''
  13.   contacts: Array<object> | null = null
  14.   key: string = ""
  15. }
  16. export let contactsGroups: object[] = [
  17.   {
  18.     title: 'A',
  19.     contacts: [
  20.       new Contact('艾佳', $r('app.media.iconA')),
  21.       new Contact('安安', $r('app.media.iconB')),
  22.       new Contact('Angela', $r('app.media.iconC')),
  23.     ],
  24.     key: util.generateRandomUUID(true)
  25.   } as ContactsGroup,
  26.   {
  27.     title: 'B',
  28.     contacts: [
  29.       new Contact('白叶', $r('app.media.iconD')),
  30.       new Contact('伯明', $r('app.media.iconE')),
  31.     ],
  32.     key: util.generateRandomUUID(true)
  33.   } as ContactsGroup,
  34.   // ...
  35. ]
  36. @Entry
  37. @Component
  38. struct ContactsList {
  39.   // 定义分组联系人数据集合contactsGroups数组
  40.   @Builder itemHead(text: string) {
  41.     // 列表分组的头部组件,对应联系人分组A、B等位置的组件
  42.     Text(text)
  43.       .fontSize(20)
  44.       .backgroundColor('#fff1f3f5')
  45.       .width('100%')
  46.       .padding(5)
  47.   }
  48.   build() {
  49.     List() {
  50.       // 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合
  51.       ForEach(contactsGroups, (itemGroup: ContactsGroup) => {
  52.         ListItemGroup({ header: this.itemHead(itemGroup.title) }) {
  53.           // 循环渲染ListItem
  54.           if (itemGroup.contacts) {
  55.             ForEach(itemGroup.contacts, (item: Contact) => {
  56.               ListItem() {
  57.                 // ...
  58.               }
  59.             }, (item: Contact) => JSON.stringify(item))
  60.           }
  61.         }
  62.       }, (itemGroup: ContactsGroup) => JSON.stringify(itemGroup))
  63.     }.sticky(StickyStyle.Header)  // 设置吸顶,实现粘性标题效果
  64.   }
  65. }
复制代码
6)控制滚动位置
控制滚动位置在实际应用中十分常见,比方当消息页列表项数量庞大,用户滚动列表到肯定位置时,希望快速滚动到列表底部或返回列表顶部。此时,可以通过控制滚动位置来实现列表的快速定位,如下图所示。

List组件初始化时,可以通过scroller参数绑定一个Scroller对象,举行列表的滚动控制。比方,用户在消息应用中,点击消息页面底部的返回顶部按钮时,就可以通过Scroller对象的scrollToIndex方法使列表滚动到指定的列表项索引位置。
起首,必要创建一个Scroller的对象listScroller。
  1. private listScroller: Scroller = new Scroller();
复制代码
然后,通过将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。在必要跳转的位置指定scrollToIndex的参数为0,表示返回列表顶部。
  1. Stack({ alignContent: Alignment.Bottom }) {
  2.   // 将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。
  3.   List({ space: 20, scroller: this.listScroller }) {
  4.     // ...
  5.   }
  6.   Button() {
  7.     // ...
  8.   }
  9.   .onClick(() => {
  10.     // 点击按钮时,指定跳转位置,返回列表顶部
  11.     this.listScroller.scrollToIndex(0)
  12.   })
  13. }
复制代码
7)相应滚动位置
很多应用必要监听列表的滚动位置变化并作出相应。比方,在接洽人列表滚动时,如果跨越了不同字母开头的分组,则侧边字母索引栏也必要更新到对应的字母位置。
除了字母索引之外,滚动列表团结多级分类索引在应用开辟过程中也很常见,比方购物应用的商品分类页面,多级分类也必要监听列表的滚动位置。

如上图所示,当接洽人列表从A滚动到B时,右侧索引栏也必要同步从选中A状态变成选中B状态。此场景可以通过监听List组件的onScrollIndex事件来实现,右侧索引栏必要利用字母表索引组件AlphabetIndexer。
在列表滚动时,根据列表此时所在的索引值位置firstIndex,重新计算字母索引栏对应字母的位置selectedIndex。由于AlphabetIndexer组件通过selected属性设置了选中项索引值,当selectedIndex变化时会触发AlphabetIndexer组件重新渲染,从而显示为选中对应字母的状态。
  1. const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',  'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];@Entry@Componentstruct ContactsList {  @State selectedIndex: number = 0;  private listScroller: Scroller = new Scroller();
  2.   build() {    Stack({ alignContent: Alignment.End }) {      List({ scroller: this.listScroller }) {}      .onScrollIndex((firstIndex: number) => {        // 根据列表滚动到的索引值,重新计算对应接洽人索引栏的位置this.selectedIndex      })      // 字母表索引组件      AlphabetIndexer({ arrayValue: alphabets, selected: 0 })        .selected(this.selectedIndex)    }  }}
复制代码
说明
计算索引值时,ListItemGroup作为一个团体占一个索引值,不计算ListItemGroup内部ListItem的索引值。

8)相应列表项侧滑
侧滑菜单在很多应用中都很常见。比方,通讯类应用通常会给消息列表提供侧滑删除功能,即用户可以通过向左侧滑列表的某一项,再点击删除按钮删除消息,如下图所示。其中,列表项头像右上角标记设置参考给列表项添加标记

ListItem的swipeAction属性可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。
在消息列表中,end参数表示设置ListItem左滑时尾端划出自定义组件,即删除按钮。在初始化end方法时,将滑动列表项的索引传入删除按钮组件,当用户点击删除按钮时,可以根据索引值来删除列表项对应的数据,从而实现侧滑删除功能。

  • 实现尾端滑出组件的构建。
    1. @Builder itemEnd(index: number) {
    2.   // 构建尾端滑出组件
    3.   Button({ type: ButtonType.Circle }) {
    4.     Image($r('app.media.ic_public_delete_filled'))
    5.       .width(20)
    6.       .height(20)
    7.   }
    8.   .onClick(() => {
    9.     // this.messages为列表数据源,可根据实际场景构造。点击后从数据源删除指定数据项。
    10.     this.messages.splice(index, 1);
    11.   })
    12. }
    复制代码
  • 绑定swipeAction属性到可左滑的ListItem上。
    1. // 构建List时,通过ForEach基于数据源this.messages循环渲染ListItem。
    2. ListItem() {
    3.   // ...
    4. }
    5. .swipeAction({
    6.   end: {
    7.     // index为该ListItem在List中的索引值。
    8.     builder: () => { this.itemEnd(index) },
    9.   }
    10. }) // 设置侧滑属性.
    复制代码
9)给列表项添加标记
添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力会合到应用内的某个区域。比方,当消息列表吸收到新消息时,通常对应的接洽人头像的右上方会出现标记,提示有若干条未读消息,如下图所示。

在ListItem中利用Badge组件可实现给列表项添加标记功能。Badge是可以附加在单个组件上用于信息标记的容器组件。
在消息列表中,若希望在接洽人头像右上角添加标记,可在实现消息列表项ListItem的接洽人头像时,将头像Image组件作为Badge的子组件。
在Badge组件中,count和position参数用于设置必要展示的消息数量和提示点显示位置,还可以通过style参数机动设置标记的样式。
  1. ListItem() {
  2.   Badge({
  3.     count: 1,
  4.     position: BadgePosition.RightTop,
  5.     style: { badgeSize: 16, badgeColor: '#FA2A2D' }
  6.   }) {
  7.     // Image组件实现消息联系人头像
  8.     // ...
  9.   }
  10. }
复制代码
10)下拉刷新与上拉刷新
页面的下拉刷新与上拉加载功能在移动应用中十分常见,比方,消息页面的内容刷新和加载。这两种操作的原理都是通过相应用户的触摸事件,在顶部大概底部显示一个刷新或加载视图,完成后再将此视图隐蔽。
以下拉刷新为例,实在现主要分成三步:

  • 监听手指按下事件,记载其初始位置的值。
  • 监听手指按压移动事件,记载并计算当前移动的位置与初始值的差值,大于0表示向下移动,同时设置一个允许移动的最大值。
  • 监听手指抬起事件,若此时移动达到最大值,则触发数据加载并显示刷新视图,加载完成后将此视图隐蔽。
    说明: 页面的下拉刷新操作推荐利用Refresh组件实现。
下拉刷新与上拉加载的具体实现可参考消息数据加载。
11)编辑列表
列表的编辑模式用途十分广泛,常见于待服务项管理、文件管理、备忘录的记载管理等应用场景。在列表的编辑模式下,新增和删除列表项是最底子的功能,其核心是对列表项对应的数据集合举行数据添加和删除。

  • 新增列表项
    如下图所示,当用户点击添加按钮时,提供用户新增列表项内容选择或填写的交互界面,用户点击确定后,列表中新增对应的项目。


    • 1、定义列表项数据布局,以待服务项管理为例,起首定义待办数据布局。
      1. //ToDo.ets
      2. import { util } from '@kit.ArkTS'
      3. export class ToDo {
      4.   key: string = util.generateRandomUUID(true);
      5.   name: string;
      6.   constructor(name: string) {
      7.     this.name = name;
      8.   }
      9. }
      复制代码
    • 2、构建列表团体布局和列表项。
      1. //ToDoListItem.ets
      2. import { ToDo } from './ToDo';
      3. @Component
      4. export struct ToDoListItem {
      5.   @Link isEditMode: boolean
      6.   @Link selectedItems: ToDo[]
      7.   private toDoItem: ToDo = new ToDo("");
      8.   build() {
      9.    Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
      10.      // ...
      11.    }
      12.    .width('100%')
      13.    .height(80)
      14.    //.padding() 根据具体使用场景设置
      15.    .borderRadius(24)
      16.    //.linearGradient() 根据具体使用场景设置
      17.    .gesture(
      18.      GestureGroup(GestureMode.Exclusive,
      19.      LongPressGesture()
      20.        .onAction(() => {
      21.          // ...
      22.        })
      23.      )
      24.    )
      25.   }
      26. }
      复制代码
    • 3、初始化待办列表数据和可选事项,最后,构建列表布局和列表项。
      1. //ToDoList.ets
      2. import { ToDo } from './ToDo';
      3. import { ToDoListItem } from './ToDoListItem';
      4. @Entry
      5. @Component
      6. struct ToDoList {
      7.   @State toDoData: ToDo[] = []
      8.   @Watch('onEditModeChange') @State isEditMode: boolean = false
      9.   @State selectedItems: ToDo[] = []
      10. private availableThings: string[] = ['读书', '运动', '旅游', '听音乐', '看电影', '唱歌']
      11.   onEditModeChange() {
      12.     if (!this.isEditMode) {
      13.       this.selectedItems = []
      14.     }
      15. }
      16.   build() {
      17.     Column() {
      18.       Row() {
      19.         if (this.isEditMode) {
      20.           Text('X')
      21.             .fontSize(20)
      22.             .onClick(() => {
      23.               this.isEditMode = false;
      24.             })
      25.             .margin({ left: 20, right: 20 })
      26.         } else {
      27.           Text('待办')
      28.             .fontSize(36)
      29.             .margin({ left: 40 })
      30.           Blank()
      31.           Text('+') //提供新增列表项入口,即给新增按钮添加点击事件
      32.             .onClick(() => {
      33.               this.getUIContext().showTextPickerDialog({
      34.                 range: this.availableThings,
      35.                 onAccept: (value: TextPickerResult) => {
      36.                   let arr = Array.isArray(value.index) ? value.index : [value.index];
      37.                   for (let i = 0; i < arr.length; i++) {
      38.                     this.toDoData.push(new ToDo(this.availableThings[arr[i]])); // 新增列表项数据toDoData(可选事项)
      39.                   }
      40.                 },
      41.               })
      42.             })
      43.         }
      44.         List({ space: 10 }) {
      45.           ForEach(this.toDoData, (toDoItem: ToDo) => {
      46.             ListItem() {
      47.               // 将toDoData的每个数据放入到以model的形式放进ListItem里
      48.               ToDoListItem({
      49.                 isEditMode: this.isEditMode,
      50.                 toDoItem: toDoItem,
      51.                 selectedItems: this.selectedItems })
      52.             }
      53.           }, (toDoItem: ToDo) => toDoItem.key.toString())
      54.         }
      55.       }
      56.     }
      57.   }
      58. }
      复制代码

  • 删除列表项
    如下图所示,当用户长按列表项进入删除模式时,提供用户删除列表项选择的交互界面,用户勾选完成后点击删除按钮,列表中删除对应的项目。


    • 列表的删除功能一般进入编辑模式后才可利用,所以必要提供编辑模式的入口。
      1. // 结构参考
      2. export class ToDo {
      3.   key: string = util.generateRandomUUID(true);
      4.   name: string;
      5.   toDoData: ToDo[] = [];
      6.   constructor(name: string) {
      7.     this.name = name;
      8.   }
      9. }
      复制代码
      1. // 实现参考
      2. Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
      3.   // ...
      4. }
      5. .gesture(
      6. GestureGroup(GestureMode.Exclusive,
      7.   LongPressGesture()
      8.     .onAction(() => {
      9.       if (!this.isEditMode) {
      10.         this.isEditMode = true; //进入编辑模式
      11.       }
      12.     })
      13.   )
      14. )
      复制代码
    • 必要相应用户的选择交互,记载要删除的列表项数据。
      在待办列表中,通过勾选框的勾选或取消勾选,相应用户勾选列表项变化,记载所有选择的列表项。
      1. // 结构参考
      2. import { util } from '@kit.ArkTS'
      3. export class ToDo {
      4. key: string = util.generateRandomUUID(true);
      5. name: string;
      6. toDoData: ToDo[] = [];
      7. constructor(name: string) {
      8.    this.name = name;
      9. }
      10. }
      复制代码
      1. // 实现参考
      2. if (this.isEditMode) {
      3.   Checkbox()
      4.     .onChange((isSelected) => {
      5.       if (isSelected) {
      6.         this.selectedItems.push(toDoList.toDoItem) // this.selectedItems为勾选时,记录选中的列表项,可根据实际场景构造
      7.       } else {
      8.         let index = this.selectedItems.indexOf(toDoList.toDoItem)
      9.         if (index !== -1) {
      10.           this.selectedItems.splice(index, 1) // 取消勾选时,则将此项从selectedItems中删除
      11.         }
      12.       }
      13.     })
      14. }
      复制代码
    • 必要相应用户点击删除按钮事件,删除列表中对应的选项
      1. // 结构参考
      2. import { util } from '@kit.ArkTS'
      3. export class ToDo {
      4.   key: string = util.generateRandomUUID(true);
      5.   name: string;
      6.   toDoData: ToDo[] = [];
      7.   constructor(name: string) {
      8.     this.name = name;
      9.   }
      10. }
      复制代码
      1. // 实现参考
      2. Button('删除')
      3.   .onClick(() => {
      4.     // this.toDoData为待办的列表项,可根据实际场景构造。点击后删除选中的列表项对应的toDoData数据
      5.     let leftData = this.toDoData.filter((item) => {
      6.       return !this.selectedItems.find((selectedItem) => selectedItem == item);
      7.     })
      8.     this.toDoData = leftData;
      9.     this.isEditMode = false;
      10.   })
      复制代码

12)长列表的处理
循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐利用数据懒加载(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。
当利用懒加载方式渲染列表时,为了更好的列表滚动体验,淘汰列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。
  1. List() {
  2.   // ...
  3. }.cachedCount(3)
复制代码
以垂直列表为例:


  • 若懒加载是用于ListItem,当列表为单列模式时,会在List显示的ListItem前后各缓存cachedCount个ListItem;若是多列模式下,会在List显示的ListItem前后各缓存cachedCount * 列数个ListItem。
  • 若懒加载是用于ListItemGroup,无论单列模式还是多列模式,都是在List显示的ListItem前后各缓存cachedCount个ListItemGroup。

    • cachedCount的增长会增大UI的CPU、内存开销。利用时必要根据实际情况,综合性能和用户体验举行调整。
    • 列表利用数据懒加载时,除了显示区域的列表项和前后缓存的列表项,其他列表项会被销毁。

13)示范代码


  • 二维列表
  • List组件嵌套滑动
  • 列表编辑效果
创建网格 (Grid/GridItem)

概述

网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种紧张自适应布局,其利用场景有九宫格图片展示日历计算器等。
约束与布局

Grid组件为网格容器,其中容器内各条目对应一个GridItem组件,如下图所示。

   说明
Grid的子组件必须是GridItem组件。
  网格布局是一种二维布局。Grid组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行大概几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件以及间距会等比例调整,从而实现网格布局的自适应能力。根据Grid的这些布局能力,可以构建出不同样式的网格布局,如下图所示。

如果Grid组件设置了宽高属性,则其尺寸为设置值。如果没有设置宽高属性,Grid组件的尺寸默认适应其父组件的尺寸。
Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况:


  • 行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐利用该种布局方式
  • 只设置行、列数量与占比中的一个:元素按照设置的方向举行排布,超出的元素可通过滚动的方式展示。
  • 行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。
设置分列方式

设置行列数量与占比

通过设置行列数量与尺寸占比可以确定网格布局的团体分列方式。Grid组件提供了rowsTemplatecolumnsTemplate属性用于设置网格布局行列数量与尺寸占比。
rowsTemplatecolumnsTemplate属性值是一个由多个空格和’数字+fr’隔断拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值巨细,用于计算该行或列在网格布局宽度上的占比,终极决定该行或列宽度。

如上图所示,构建的是一个三行三列的网格布局,其在垂直方向上分为三等份,每行占一份;在水平方向上分为四等份,第一列占一份,第二列占两份,第三列占一份。
只要将rowsTemplate的值为’1fr 1fr 1fr’,同时将columnsTemplate的值为’1fr 2fr 1fr’,即可实现上述网格布局。
  1. Grid() {
  2.   ...
  3. }
  4. .rowsTemplate('1fr 1fr 1fr')
  5. .columnsTemplate('1fr 2fr 1fr')
复制代码
  说明
当Grid组件设置了rowsTemplate或columnsTemplate时,Grid的layoutDirection、maxCount、minCount、cellLength属性不生效,属性说明可参考Grid-属性。
  设置子组件所占行列数

除了巨细相同的等比例网格布局,由不同巨细的网格组成不均匀分布的网格布局场景在实际应用中十分常见,如下图所示。在Grid组件中,可以通过创建Grid时传入合适的GridLayoutOptions实现如图所示的单个网格横跨多行或多列的场景,其中,irregularIndexes和onGetIrregularSizeByIndex可对仅设置rowsTemplate或columnsTemplate的Grid利用;onGetRectByIndex可对同时设置rowsTemplate和columnsTemplate的Grid利用。

比方: 计算器的按键布局就是常见的不均匀网格布局场景。如下图,计算器中的按键“0”和“=”,按键“0”横跨第一、二两列,按键“=”横跨第五、六两行。利用Grid构建的网格布局,其行列标号从0开始,依次编号。

在网格中,可以通过onGetRectByIndex返回的**[rowStart,columnStart,rowSpan,columnSpan]**来实现跨行跨列布局,其中rowStart和columnStart属性表示指定当前元素起始行号和起始列号rowSpan和columnSpan属性表示指定当前元素的占用行数和占用列数
所以“0”按键横跨第一列和第二列,“=”按键横跨第五行和第六行,只要将
“0”对应onGetRectByIndex的rowStart和columnStart设为5和0,rowSpan和columnSpan设为1和2,将
“=”对应onGetRectByIndex的rowStart和columnStart设为4和3,rowSpan和columnSpan设为2和1即可
  1. layoutOptions: GridLayoutOptions = {
  2.   regularSize: [1, 1],
  3.   onGetRectByIndex: (index: number) => {
  4.     if (index == key1) { // key1是“0”按键对应的index
  5.       return [5, 0, 1, 2]
  6.     } else if (index == key2) { // key2是“=”按键对应的index
  7.       return [4, 3, 2, 1]
  8.     }
  9.     // ...
  10.     // 这里需要根据具体布局返回其他item的位置
  11.   }
  12. }
  13. Grid(undefined, this.layoutOptions) {
  14.   // ...
  15. }
  16. .columnsTemplate('1fr 1fr 1fr 1fr')
  17. .rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
复制代码
设置主轴方向

利用Grid构建网格布局时,若没有设置行列数量与占比,可以通过layoutDirection设置网格布局的主轴方向,决定子组件的分列方式。此时可以团结minCount和maxCount属性来约束主轴方向上的网格数量。

当前layoutDirection设置为Row时,先从左到右分列,排满一行再排下一行。当前layoutDirection设置为Column时,先从上到下分列,排满一列再排下一列,如上图所示。此时,将maxCount属性设为3,表示主轴方向上最大显示的网格单元数量为3。
  1. Grid() {
  2.   ...
  3. }
  4. .maxCount(3)
  5. .layoutDirection(GridDirection.Row)
复制代码
  说明
layoutDirection属性仅在不设置rowsTemplate和columnsTemplate时生效,此时元素在layoutDirection方向上分列。
仅设置rowsTemplate时,Grid主轴为水平方向,交错轴为垂直方向。
仅设置columnsTemplate时,Grid主轴为垂直方向,交错轴为水平方向。
  在网格布局中显示数据

网格布局采用二维布局的方式组织其内部元素,如下图所示。

  1. Grid() {
  2.   GridItem() {
  3.     Text('会议')
  4.       ...
  5.   }
  6.   GridItem() {
  7.     Text('签到')
  8.       ...
  9.   }
  10.   GridItem() {
  11.     Text('投票')
  12.       ...
  13.   }
  14.   GridItem() {
  15.     Text('打印')
  16.       ...
  17.   }
  18. }
  19. .rowsTemplate('1fr 1fr')
  20. .columnsTemplate('1fr 1fr')
复制代码
对于内容布局相似的多个GridItem,通常更推荐利用ForEach语句中嵌套GridItem的形式,来淘汰重复代码。
  1. @Entry
  2. @Component
  3. struct OfficeService {
  4.   @State services: Array<string> = ['会议', '投票', '签到', '打印']
  5.   build() {
  6.     Column() {
  7.       Grid() {
  8.         ForEach(this.services, (service:string) => {
  9.           GridItem() {
  10.             Text(service)
  11.           }
  12.         }, (service:string):string => service)
  13.       }
  14.       .rowsTemplate(('1fr 1fr') as string)
  15.       .columnsTemplate(('1fr 1fr') as string)
  16.     }
  17.   }
  18. }
复制代码
设置行列间距

在两个网格单元之间的网格横向间距称为行间距,网格纵向间距称为列间距,如下图所示。

通过Grid的rowsGapcolumnsGap可以设置网格布局的行列间距。在图5所示的计算器中,行间距为15vp,列间距为10vp。
  1. Grid() {
  2.   ...
  3. }
  4. .columnsGap(10)
  5. .rowsGap(15)
复制代码
构建可滚动的网格布局

可滚动的网格布局常用在文件管理、购物或视频列表等页面中,如下图所示。在设置Grid的行列数量与占比时,如果仅设置行、列数量与占比中的一个,即仅设置rowsTemplate或仅设置columnsTemplate属性,网格单元按照设置的方向分列,超出Grid显示区域后,Grid拥有可滚动能力。

如果设置的是columnsTemplate,Grid的滚动方向为垂直方向;如果设置的是rowsTemplate,Grid的滚动方向为水平方向
如上图所示的横向可滚动网格布局,只要设置rowsTemplate属性的值且不设置columnsTemplate属性,当内容超出Grid组件宽度时,Grid可横向滚动举行内容展示。
  1. @Entry
  2. @Component
  3. struct Shopping {
  4.   @State services: Array<string> = ['直播', '进口']
  5.   build() {
  6.     Column({ space: 5 }) {
  7.       Grid() {
  8.         ForEach(this.services, (service: string, index) => {
  9.           GridItem() {
  10.           }
  11.           .width('25%')
  12.         }, (service:string):string => service)
  13.       }
  14.       .rowsTemplate('1fr 1fr') // 只设置rowsTemplate属性,当内容超出Grid区域时,可水平滚动。
  15.       .rowsGap(15)
  16.     }
  17.   }
  18. }
复制代码
控制滚动位置

与消息列表的返回顶部场景类似,控制滚动位置功能在网格布局中也很常用,比方下图所示日历的翻页功能。

Grid组件初始化时,可以绑定一个Scroller对象,用于举行滚动控制,比方通过Scroller对象的scrollPage方法举行翻页。
  1. private scroller: Scroller = new Scroller()
复制代码
在日历页面中,用户在点击“下一页”按钮时,应用相应点击事件,通过指定scrollPage方法的参数next为true,滚动到下一页。
  1. Column({ space: 5 }) {
  2.   Grid(this.scroller) {
  3.   }
  4.   .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
  5.   Row({space: 20}) {
  6.     Button('上一页')
  7.       .onClick(() => {
  8.         this.scroller.scrollPage({
  9.           next: false
  10.         })
  11.       })
  12.     Button('下一页')
  13.       .onClick(() => {
  14.         this.scroller.scrollPage({
  15.           next: true
  16.         })
  17.       })
  18.   }
  19. }
复制代码
性能优化

长列表的处理类似循环渲染适用于数据量较小的布局场景,当构建具有大量网格项的可滚动网格布局时,推荐利用数据懒加载方式实现按需迭代加载数据,从而提升列表性能。
  1. Grid() {
  2.   LazyForEach(this.dataSource, () => {
  3.     GridItem() {
  4.     }
  5.   })
  6. }
  7. .cachedCount(3)
复制代码
创建轮播 (Swiper)

Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件举行轮播显示。通常,在一些应用首页显示推荐的内容时,必要用到轮播显示的能力。
布局与约束

Swiper作为一个容器组件,如果设置了自身尺寸属性,则在轮播显示过程中均以该尺寸生效。如果自身尺寸属性未被设置,则分两种情况:如果设置了prevMargin大概nextMargin属性,则Swiper自身尺寸会跟随其父组件;如果未设置prevMargin大概nextMargin属性,则会主动根据子组件的巨细设置自身的尺寸。
循环播放

通过loop属性控制是否循环播放,该属性默认值为true。
当loop为true时,在显示第一页或最后一页时,可以继承往前切换到前一页大概往后切换到后一页。如果loop为false,则在第一页或最后一页时,无法继承向前大概向后切换页面。


  • loop为true
    1. Swiper() {
    2.   Text('0')
    3.     .width('90%')
    4.     .height('100%')
    5.     .backgroundColor(Color.Gray)
    6.     .textAlign(TextAlign.Center)
    7.     .fontSize(30)
    8.   Text('1')
    9.     .width('90%')
    10.     .height('100%')
    11.     .backgroundColor(Color.Green)
    12.     .textAlign(TextAlign.Center)
    13.     .fontSize(30)
    14.   Text('2')
    15.     .width('90%')
    16.     .height('100%')
    17.     .backgroundColor(Color.Pink)
    18.     .textAlign(TextAlign.Center)
    19.     .fontSize(30)
    20. }
    21. .loop(true)
    复制代码

  • loop为false
    1. Swiper() {
    2.   // ...
    3. }
    4. .loop(false)
    复制代码

主动轮播

Swiper通过设置autoPlay属性,控制是否主动轮播子组件。该属性默认值为false。
autoPlay为true时,会主动切换播放子组件,子组件与子组件之间的播放隔断通过interval属性设置。interval属性默认值为3000,单元毫秒。
  1. Swiper() {
  2.   // ...
  3. }
  4. .loop(true)
  5. .autoPlay(true)
  6. .interval(1000)
复制代码

导航点样式

Swiper提供了默认的导航点样式和导航点箭头样式,导航点默认显示在Swiper下方居中位置,开辟者也可以通过indicator属性自定义导航点的位置和样式,导航点箭头默认不显示
通过indicator属性,开辟者可以设置导航点相对于Swiper组件上下左右四个方位的位置,同时也可以设置每个导航点的尺寸、颜色、蒙层和被选中导航点的颜色。


  • 导航点利用默认样式
    1. Swiper() {
    2.   Text('0')
    3.     .width('90%')
    4.     .height('100%')
    5.     .backgroundColor(Color.Gray)
    6.     .textAlign(TextAlign.Center)
    7.     .fontSize(30)
    8.   Text('1')
    9.     .width('90%')
    10.     .height('100%')
    11.     .backgroundColor(Color.Green)
    12.     .textAlign(TextAlign.Center)
    13.     .fontSize(30)
    14.   Text('2')
    15.     .width('90%')
    16.     .height('100%')
    17.     .backgroundColor(Color.Pink)
    18.     .textAlign(TextAlign.Center)
    19.     .fontSize(30)
    20. }
    复制代码

  • 自定义导航点样式
    导航点直径设为30vp,左边距为0,导航点颜色设为赤色。
    1. Swiper() {
    2.   // ...
    3. }
    4. .indicator(
    5.   Indicator.dot()
    6.     .left(0)
    7.     .itemWidth(15)
    8.     .itemHeight(15)
    9.     .selectedItemWidth(30)
    10.     .selectedItemHeight(15)
    11.     .color(Color.Red)
    12.     .selectedColor(Color.Blue)
    13. )
    复制代码

Swiper通过设置displayArrow属性,可以控制导航点箭头的巨细、位置、颜色,底板的巨细及颜色,以及鼠标悬停时是否显示箭头。


  • 箭头利用默认样式
    1. Swiper() {
    2.   // ...
    3. }
    4. .displayArrow(true, false)
    复制代码

  • 自定义箭头样式
    箭头显示在组件两侧,巨细为18vp,导航点箭头颜色设为蓝色。
    1. Swiper() {
    2.   // ...
    3. }
    4. .displayArrow({
    5.   showBackground: true,
    6.   isSidebarMiddle: true,
    7.   backgroundSize: 24,
    8.   backgroundColor: Color.White,
    9.   arrowSize: 18,
    10.   arrowColor: Color.Blue
    11.   }, false)
    复制代码

页面切换方式

Swiper支持手指滑动、点击导航点和通过控制器三种方式切换页面,以下示例展示通过控制器切换页面的方法。
  1. @Entry
  2. @Component
  3. struct SwiperDemo {
  4.   private swiperController: SwiperController = new SwiperController();
  5.   build() {
  6.     Column({ space: 5 }) {
  7.       Swiper(this.swiperController) {
  8.         Text('0')
  9.           .width(250)
  10.           .height(250)
  11.           .backgroundColor(Color.Gray)
  12.           .textAlign(TextAlign.Center)
  13.           .fontSize(30)
  14.         Text('1')
  15.           .width(250)
  16.           .height(250)
  17.           .backgroundColor(Color.Green)
  18.           .textAlign(TextAlign.Center)
  19.           .fontSize(30)
  20.         Text('2')
  21.           .width(250)
  22.           .height(250)
  23.           .backgroundColor(Color.Pink)
  24.           .textAlign(TextAlign.Center)
  25.           .fontSize(30)
  26.       }
  27.       .indicator(true)
  28.       Row({ space: 12 }) {
  29.         Button('showNext')
  30.           .onClick(() => {
  31.             this.swiperController.showNext(); // 通过controller切换到后一页
  32.           })
  33.         Button('showPrevious')
  34.           .onClick(() => {
  35.             this.swiperController.showPrevious(); // 通过controller切换到前一页
  36.           })
  37.       }.margin(5)
  38.     }.width('100%')
  39.     .margin({ top: 5 })
  40.   }
  41. }
复制代码

轮播方向

Swiper支持水平和垂直方向上举行轮播,主要通过vertical属性控制
当vertical为true时,表示在垂直方向上举行轮播;为false时,表示在水平方向上举行轮播。vertical默认值为false。


  • 设置水平方向轮播
  1. Swiper() {
  2.   // ...
  3. }
  4. .indicator(true)
  5. .vertical(false)
复制代码



  • 设置垂直方向轮播
  1. Swiper() {
  2.   // ...
  3. }
  4. .indicator(true)
  5. .vertical(true)
复制代码

每页显示多个子页面

Swiper支持在一个页面内同时显示多个子组件,通过displayCount属性设置。
  1. Swiper() {
  2.   Text('0')
  3.     .width(250)
  4.     .height(250)
  5.     .backgroundColor(Color.Gray)
  6.     .textAlign(TextAlign.Center)
  7.     .fontSize(30)
  8.   Text('1')
  9.     .width(250)
  10.     .height(250)
  11.     .backgroundColor(Color.Green)
  12.     .textAlign(TextAlign.Center)
  13.     .fontSize(30)
  14.   Text('2')
  15.     .width(250)
  16.     .height(250)
  17.     .backgroundColor(Color.Pink)
  18.     .textAlign(TextAlign.Center)
  19.     .fontSize(30)
  20.   Text('3')
  21.     .width(250)
  22.     .height(250)
  23.     .backgroundColor(Color.Blue)
  24.     .textAlign(TextAlign.Center)
  25.     .fontSize(30)
  26. }
  27. .indicator(true)
  28. .displayCount(2)
复制代码

自定义切换动画

Swiper支持通过customContentTransition设置自定义切换动画,可以在回调中对视窗内所有页面逐帧设置透明度、缩放比例、位移、渲染层级等属性实现自定义切换动画。
  1. @Entry
  2. @Component
  3. struct SwiperCustomAnimationExample {
  4.   private DISPLAY_COUNT: number = 2
  5.   private MIN_SCALE: number = 0.75
  6.   @State backgroundColors: Color[] = [Color.Green, Color.Blue, Color.Yellow, Color.Pink, Color.Gray, Color.Orange]
  7.   @State opacityList: number[] = []
  8.   @State scaleList: number[] = []
  9.   @State translateList: number[] = []
  10.   @State zIndexList: number[] = []
  11.   aboutToAppear(): void {
  12.     for (let i = 0; i < this.backgroundColors.length; i++) {
  13.       this.opacityList.push(1.0)
  14.       this.scaleList.push(1.0)
  15.       this.translateList.push(0.0)
  16.       this.zIndexList.push(0)
  17.     }
  18.   }
  19.   build() {
  20.     Column() {
  21.       Swiper() {
  22.         ForEach(this.backgroundColors, (backgroundColor: Color, index: number) => {
  23.           Text(index.toString()).width('100%').height('100%').fontSize(50).textAlign(TextAlign.Center)
  24.             .backgroundColor(backgroundColor)
  25.             .opacity(this.opacityList[index])
  26.             .scale({ x: this.scaleList[index], y: this.scaleList[index] })
  27.             .translate({ x: this.translateList[index] })
  28.             .zIndex(this.zIndexList[index])
  29.         })
  30.       }
  31.       .height(300)
  32.       .indicator(false)
  33.       .displayCount(this.DISPLAY_COUNT, true)
  34.       .customContentTransition({
  35.         timeout: 1000,
  36.         transition: (proxy: SwiperContentTransitionProxy) => {
  37.           if (proxy.position <= proxy.index % this.DISPLAY_COUNT || proxy.position >= this.DISPLAY_COUNT + proxy.index % this.DISPLAY_COUNT) {
  38.             // 同组页面完全滑出视窗外时,重置属性值
  39.             this.opacityList[proxy.index] = 1.0
  40.             this.scaleList[proxy.index] = 1.0
  41.             this.translateList[proxy.index] = 0.0
  42.             this.zIndexList[proxy.index] = 0
  43.           } else {
  44.             // 同组页面未滑出视窗外时,对同组中左右两个页面,逐帧根据position修改属性值
  45.             if (proxy.index % this.DISPLAY_COUNT === 0) {
  46.               this.opacityList[proxy.index] = 1 - proxy.position / this.DISPLAY_COUNT
  47.               this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position / this.DISPLAY_COUNT)
  48.               this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
  49.             } else {
  50.               this.opacityList[proxy.index] = 1 - (proxy.position - 1) / this.DISPLAY_COUNT
  51.               this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - (proxy.position - 1) / this.DISPLAY_COUNT)
  52.               this.translateList[proxy.index] = - (proxy.position - 1) * proxy.mainAxisLength - (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
  53.             }
  54.             this.zIndexList[proxy.index] = -1
  55.           }
  56.         }
  57.       })
  58.     }.width('100%')
  59.   }
  60. }
复制代码

示例代码

短视频切换
选项卡 (Tabs)

当页面信息较多时,为了让用户可以或许聚焦于当前显示的内容,必要对页面内容举行分类,进步页面空间利用率。Tabs组件可以在一个页面内快速实现视图内容的切换,一方面提升查找信息的效率,另一方面精简用户单次获取到的信息量。
根本布局

Tabs组件的页面组成包含两个部分,分别是TabContentTabBarTabContent是内容页,TabBar是导航页签栏,页面布局如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。

   说明
TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。
  每一个TabContent对应的内容必要有一个页签,可以通过TabContent的tabBar属性举行配置。在如下TabContent组件上设置tabBar属性,可以设置其对应页签中的内容,tabBar作为内容的页签。
  1. TabContent() {
  2.    Text('首页的内容').fontSize(30)
  3. }
  4. .tabBar('首页')
复制代码
设置多个内容时,需在Tabs内按照顺序放置。
  1. Tabs() {
  2.   TabContent() {
  3.     Text('首页的内容').fontSize(30)
  4.   }
  5.   .tabBar('首页')
  6.   TabContent() {
  7.     Text('推荐的内容').fontSize(30)
  8.   }
  9.   .tabBar('推荐')
  10.   TabContent() {
  11.     Text('发现的内容').fontSize(30)
  12.   }
  13.   .tabBar('发现')
  14.   
  15.   TabContent() {
  16.     Text('我的内容').fontSize(30)
  17.   }
  18.   .tabBar("我的")
  19. }
复制代码
底部导航

底部导航是应用中最常见的一种导航方式。底部导航位于应用一级页面的底部,用户打开应用,可以或许分清整个应用的功能分类,以及页签对应的内容,而且其位于底部更加方便用户单手操作。底部导航一般作为应用的主导航形式存在,其作用是将用户关心的内容按照功能举行分类,迎合用户利用风俗,方便在不同模块间的内容切换。

导航栏位置利用Tabs的barPosition参数举行设置默认情况下,导航栏位于顶部,此时,barPosition为BarPosition.Start设置为底部导航时,必要将barPosition设置为BarPosition.End
  1. Tabs({ barPosition: BarPosition.End }) {
  2.   // TabContent的内容:首页、发现、推荐、我的
  3.   ...
  4. }
复制代码
顶部导航

当内容分类较多,用户对不同内容的浏览概率相差不大,必要经常快速切换时,一般采用顶部导航模式举行设计,作为对底部导航内容的进一步划分,常见一些资讯类应用对内容的分类为关注、视频、数码,大概主题应用中对主题举行进一步划分为图片、视频、字体等。

  1. Tabs({ barPosition: BarPosition.Start }) {
  2.   // TabContent的内容:关注、视频、游戏、数码、科技、体育、影视
  3.   ...
  4. }
复制代码
侧边导航

侧边导航是应用较为少见的一种导航模式,更多适用于横屏界面,用于对应用举行导航操作,由于用户的视觉风俗是从左到右,侧边导航栏默认为左侧侧边栏。

实现侧边导航栏必要将Tabs的vertical属性设置为true,vertical默认值为false,表明内容页和导航栏垂直方向分列。
  1. Tabs({ barPosition: BarPosition.Start }) {
  2.   // TabContent的内容:首页、发现、推荐、我的
  3.   ...
  4. }
  5. .vertical(true)
  6. .barWidth(100)
  7. .barHeight(200)
复制代码
  说明
vertical为false时,tabbar的宽度默认为撑满屏幕的宽度,必要设置barWidth为合适值。
vertical为true时,tabbar的高度默认为实际内容的高度,必要设置barHeight为合适值。
  限制导航栏的滑动切换

默认情况下,导航栏都支持滑动切换,在一些内容信息量必要举行多级分类的页面,如支持底部导航+顶部导航组合的情况下,底部导航栏的滑动效果与顶部导航出现冲突,此时必要限制底部导航的滑动,避免引起不好的用户体验。

控制滑动切换的属性为scrollable,默认值为true,表示可以滑动,若要限制滑动切换页签则必要设置为false。
  1. Tabs({ barPosition: BarPosition.End }) {
  2.   TabContent(){
  3.     Column(){
  4.       Tabs(){
  5.         // 顶部导航栏内容
  6.         ...
  7.       }
  8.     }
  9.     .backgroundColor('#ff08a8f1')
  10.     .width('100%')
  11.   }
  12.   .tabBar('首页')
  13.   // 其他TabContent内容:发现、推荐、我的
  14.   ...
  15. }
  16. .scrollable(false)
复制代码
固定导航栏

当内容分类较为固定且不具有拓展性时,比方底部导航内容分类一般固定,分类数量一般在3-5个,此时利用固定导航栏。固定导航栏不可滚动,无法被拖拽滚动,内容均分tabBar的宽度。
Tabs的barMode属性用于控制导航栏是否可以滚动,默认值为BarMode.Fixed。
  1. Tabs({ barPosition: BarPosition.End }) {
  2.   // TabContent的内容:首页、发现、推荐、我的
  3.   ...
  4. }
  5. .barMode(BarMode.Fixed)
复制代码
滚动导航栏

滚动导航栏必要设置Tabs组件的barMode属性,默认值为BarMode.Fixed表示为固定导航栏,BarMode.Scrollable表示可滚动导航栏
  1. Tabs({ barPosition: BarPosition.Start }) {
  2.   // TabContent的内容:关注、视频、游戏、数码、科技、体育、影视、人文、艺术、自然、军事
  3.   ...
  4. }
  5. .barMode(BarMode.Scrollable)
复制代码
自定义导航栏

对于底部导航栏,一般作为应用主页面功能区分,为了更好的用户体验,会组合文字以及对应语义图标表示页签内容,这种情况下,必要自定义导航页签的样式。

设置自定义导航栏必要利用tabBar的参数,以其支持的CustomBuilder的方式传入自定义的函数组件样式。比方这里声明tabBuilder的自定义函数组件,传入参数包罗页签文字title,对应位置index,以及选中状态和未选中状态的图片资源。通过当前活泼的currentIndex和页签对应的targetIndex匹配与否,决定UI显示的样式。
  1. @Builder tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
  2.   Column() {
  3.     Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
  4.       .size({ width: 25, height: 25 })
  5.     Text(title)
  6.       .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
  7.   }
  8.   .width('100%')
  9.   .height(50)
  10.   .justifyContent(FlexAlign.Center)
  11. }
复制代码
在TabContent对应tabBar属性中传入自定义函数组件,并传递相应的参数。
  1. TabContent() {
  2.   Column(){
  3.     Text('我的内容')  
  4.   }
  5.   .width('100%')
  6.   .height('100%')
  7.   .backgroundColor('#007DFF')
  8. }
  9. .tabBar(this.tabBuilder('我的', 0, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
复制代码
切换至指定页签

在不利用自定义导航栏时,默认的Tabs会实现切换逻辑。在利用了自定义导航栏后默认的Tabs仅实现滑动内容页和点击页签时内容页的切换逻辑,页签切换逻辑必要自行实现。即用户滑动内容页和点击页签时,页签栏必要同步切换至内容页对应的页签。
内容页和页签不联动

此时必要利用Tabs提供的onChange事件方法,监听索引index的变化,并将当前活泼的index值传递给currentIndex,实现页签的切换。
  1. @Entry
  2. @Component
  3. struct TabsExample1 {
  4.   @State currentIndex: number = 2
  5.   @Builder tabBuilder(title: string, targetIndex: number) {
  6.     Column() {
  7.       Text(title)
  8.         .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
  9.     }
  10.   }
  11.   build() {
  12.     Column() {
  13.       Tabs({ barPosition: BarPosition.End }) {
  14.         TabContent() {
  15.           ...
  16.         }.tabBar(this.tabBuilder('首页', 0))
  17.         TabContent() {
  18.           ...
  19.         }.tabBar(this.tabBuilder('发现', 1))
  20.         TabContent() {
  21.           ...
  22.         }.tabBar(this.tabBuilder('推荐', 2))
  23.         TabContent() {
  24.           ...
  25.         }.tabBar(this.tabBuilder('我的', 3))
  26.       }
  27.       .animationDuration(0)
  28.       .backgroundColor('#F1F3F5')
  29.       .onChange((index: number) => {
  30.         this.currentIndex = index
  31.       })
  32.     }.width('100%')
  33.   }
  34. }
复制代码
开辟应用沉浸式效果

概述

典型应用全屏窗口UI元素包罗状态栏应用界面底部导航条,其中状态栏和导航条,通常在沉浸式布局下称为避让区;避让区之外的区域称为安全区。开辟应用沉浸式效果主要指通过调整状态栏、应用界面和导航条的显示效果来淘汰状态栏导航条等系统界面的突兀感,从而利用户获得最佳的UI体验。

开辟应用沉浸式效果主要要考虑如下几个设计要素:


  • UI元素避让处理:导航条底部区域可以相应点击事件,除此之外的可交互UI元素和应用关键信息不建议放到导航条区域。状态栏显示系统信息,如果与界面元素有冲突,必要考虑避让状态栏。
  • 沉浸式效果处理:将状态栏和导航条颜色与界面元素颜色相匹配,不出现显着的突兀感。
针对上面的设计要求,可以通过如下两种方式实现应用沉浸式效果:


  • 窗口全屏布局方案
    调整布局系统为全屏布局,界面元素延伸到状态栏和导航条区域实现沉浸式效果。当不隐蔽避让区时,可通过接口查询状态栏和导航条区域举行可交互元素避让处理,并设置状态栏或导航条的颜色等属性与界面元素匹配。当隐蔽避让区时,通过对应接口设置全屏布局即可。
  • 组件安全区方案
    布局系统保持安全区内布局,然后通过接口延伸绘制内容(如配景致,配景图)到状态栏和导航条区域实现沉浸式效果。
窗口全屏布局方案

窗口全屏布局方案主要涉及以下应用扩展布局,全屏显示,不隐蔽避让区和应用扩展布局,隐蔽避让区两个应用场景。
应用扩展布局,全屏显示,不隐蔽避让区

可以通过调用窗口强制全屏布局接口setWindowLayoutFullScreen() 实现界面元素延伸到状态栏和导航条;然后通过接口getWindowAvoidArea()on(‘avoidAreaChange’) 获取并动态监听避让区域的变更信息,页面布局根据避让区域信息举行动态调整;设置状态栏或导航条的颜色等属性与界面元素举行匹配。

  • 调用setWindowLayoutFullScreen()接口设置窗口全屏。
    1. // EntryAbility.ets
    2. import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    3. import { window } from '@kit.ArkUI';
    4. import { BusinessError } from '@kit.BasicServicesKit';
    5. export default class EntryAbility extends UIAbility {
    6.   // ...
    7.   onWindowStageCreate(windowStage: window.WindowStage): void {
    8.     windowStage.loadContent('pages/Index', (err, data) => {
    9.       if (err.code) {
    10.         return;
    11.       }
    12.       let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口
    13.       // 1. 设置窗口全屏
    14.       let isLayoutFullScreen = true;
    15.       windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {
    16.         console.info('Succeeded in setting the window layout to full-screen mode.');
    17.       }).catch((err: BusinessError) => {
    18.         console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
    19.       });
    20.       // 进行后续步骤2-3中的操作
    21.     });
    22.   }
    23. }
    复制代码
  • 利用getWindowAvoidArea()接口获取当前布局遮挡区域(比方状态栏、导航条)。
    1. // EntryAbility.ets
    2. // 2. 获取布局避让遮挡的区域
    3. let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
    4. let avoidArea = windowClass.getWindowAvoidArea(type);
    5. let bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航条区域的高度
    6. AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
    7. type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例
    8. avoidArea = windowClass.getWindowAvoidArea(type);
    9. let topRectHeight = avoidArea.topRect.height; // 获取状态栏区域高度     
    10. AppStorage.setOrCreate('topRectHeight', topRectHeight);
    复制代码
  • 注册监听函数,动态获取避让区域的实时数据。常见的触发避让区回调的场景如下:应用窗口在全屏模式、悬浮模式、分屏模式之间的切换;应用窗口旋转;多折叠设备在屏幕折叠态和睁开态之间的切换;应用窗口在多设备之间的流转。
    1. // EntryAbility.ets
    2. // 3. 注册监听函数,动态获取避让区域数据
    3. windowClass.on('avoidAreaChange', (data) => {
    4.   if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
    5.     let topRectHeight = data.area.topRect.height;
    6.     AppStorage.setOrCreate('topRectHeight', topRectHeight);
    7.   } else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
    8.     let bottomRectHeight = data.area.bottomRect.height;
    9.     AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
    10.   }
    11. });
    复制代码
  • 布局中的UI元素必要避让状态栏和导航条,否则大概产生UI元素重叠等情况。
           说明
    避让区域存在巨细为0的情况,当获取到的避让区域为0时,开辟者需注意针对性处理适配此时的页面区域和布局,避免贴边、内容裁剪等题目,影相应用界面正常显示或雅观性。
应用扩展布局,隐蔽避让区

组件安全区方案


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表