【HarmonyOSNext】常用布局组件篇 - 详细介绍线性布局、弹性布局、层叠布局与网格布局以及其实现组件

556 阅读12分钟

在本篇文章中,我将向你介绍ArkUI中常用的布局方式,与其实现组件,并教学如何使用它们来构建出色的用户界面。通过学习和实践这些组件,你将能够创建出美观、直观且易于使用的应用程序。

常用布局方式与实现组件

1、线性布局

首先,我们先需要了解一下什么叫线性布局,就像字面意思一样,线性布局下的内容排列是呈线性的,具体的排列方向由线性布局组件的主轴来决定。

线性布局作为开发中最常用的布局方式,由Column组件和Row组件来实现。

1.1、Column容器组件

Column容器组件的主轴是垂直的,容器内的子元素在容器内按照垂直方向依次排列。

子元素排列示意图:

Column容器内子元素排列示意图

1.2、Row容器组件

Row容器组件的主轴是水平的,容器内的子元素在容器内按照水平方向依次排列。

子元素排列示意图:

Row容器内子元素排列示意图

1.3、布局子元素在排列方向上的间距

在上面的效果图中我们可以看到,容器内的子元素是存在间距的。如果我们在使用布局容器的时候子元素不设置外间距,那么所有子元素都会紧挨在一起,就像下图一样。

image.png

当我们需要给每个子元素之间都设置间距的时候就可以通过给布局组件设置space参数来设置间距。ColumnRow组件都可以通过这种方式来设置间距。

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Blue)
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Orange)
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Pink)
    }
    .width('100%')
    .height('100%')
  }
}

效果图如下:

image.png

1.4、布局子元素在主轴上的排列方式

有时候我们在布局的时候会有需要子组件两端对齐的时候,比如在下图中,我们可以分析出来最外层使用的是Row组件来横向布局,里面又包含两个子组件,一个在最左边一个在最右边,这时候我们虽然也可以通过space来设置间距,但是因为距离不好判断,非常的不方便,所以这个时候我们就可以通过布局容器的justifyContent属性来设置子元素在主轴上的对齐方式。

image.png

justifyContent属性的值是一个枚举类型:FlexAlign

名称描述
Start首端对齐
Center居中对齐
End尾部对齐
SpaceBetween两端对齐子元素之间间距相等
SpaceAround子元素两侧间距相等第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半
SpaceEvenly相邻元素之间的距离、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样
@Entry
@Component
struct Index {
  build() {
    Column() {
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Blue)
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Orange)
      Row() {

      }
      .width('100%')
      .height(100)
      .backgroundColor(Color.Pink)
    }
    .width('100%')
    .height('100%')
    // .justifyContent(FlexAlign.Start)
    // .justifyContent(FlexAlign.Center)
    // .justifyContent(FlexAlign.End)
    // .justifyContent(FlexAlign.SpaceBetween)
    // .justifyContent(FlexAlign.SpaceAround)
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

子元素排列示意图:

image.png

1.5、布局子元素在交叉轴上的对齐方式

布局容器除了主轴之外,还有一个交叉轴,交叉轴就是和主轴相垂直的轴。Column组件的交叉轴是水平的,Row组件的交叉轴是垂直的。我们可以通过alignItems属性来设置子元素在交叉轴上的对齐方式。

因为Column组件和Row组件的交叉轴是不一样的,所以对应alignItems属性的值也不一样,Column组件alignItems属性的取值是一个枚举类型:HorizontalAlign,取值为StartCenterEndRow组件alignItems属性的取值是一个枚举类型:VerticalAlign,取值为TopCenterBottom,对应示意图如下:

image.png

image.png

1.6、自适应缩放

在前面我们学习了主轴的对齐方式,当时的案例我们还有一种解决办法,就是使用自适应缩放。我们可以重新分析一下这个布局,将左边的容器的宽度看作占满了除了右边按钮的空间。

但是我们并不知道这个占满剩余空间的宽度是多少,这个时候我们就可以使用自适应缩放layoutWeight属性设置为1来实现效果。属性后面跟的数值代表着占剩余空间的几分之1。

image.png

@Entry
@Component
struct Index {
  build() {
    Column() {
      Row() {
        Row({ space: 10 }) {
          Text('知乎')
            .width(40)
            .height(40)
            .fontColor(Color.White)
            .textAlign(TextAlign.Center)
            .borderRadius(7)
            .backgroundColor('#0077ff')
          Column({ space: 5 }) {
            Text('知乎')
            Text('有问题就会有答案')
              .fontSize(12)
              .fontColor('#999')
          }
          .alignItems(HorizontalAlign.Start)
        }
        .layoutWeight(1)
        Text('打开App')
          .width(90)
          .height(35)
          .backgroundColor('#0077ff')
          .textAlign(TextAlign.Center)
          .fontColor(Color.White)
          .borderRadius(20)
      }
      .width('100%')
      .height(70)
      .padding(10)
    }
    .width('100%')
    .height('100%')
  }
}

2、弹性布局

弹性布局由Flex组件来实现,弹性布局分为单行布局和多行布局。默认情况下,容器中的子元素沿主轴线排列。当子元素尺寸的总和大于容器尺寸的时侯,子元素尺寸会自动挤压。如下图所示:

image.png

2.1、布局方向

Flex组件默认存在主轴和交叉轴,主轴默认是水平方向,如果想要修改主轴方向可以通过设置direction参数来设置。

比如在下图中,容器内的子组件默认是水平排列的,如果我们想让他垂直排列就可以设置direction: FlexDirection.Column参数来实现垂直排列。

image.png

@Entry
@Component
struct Index {
  build() {
    Column() {
      Flex({ direction: FlexDirection.Column }) {
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Blue)
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Orange)
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Pink)
      }
    }
    .width('100%')
    .height('100%')
  }
}

image.png

2.2、布局换行

如果我们想要实现如下图所示的效果,默认情况下,因为子组件内容长度总和超过Flex组件的话会压缩子组件的宽度,所以是达不成下面效果的,这个时候我们就可以通过设置wrap: FlexWrap.Wrap参数来实现换行的效果。

image.png

import { LengthMetrics } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 }) {
      Text('搜索发现')
        .fontWeight(FontWeight.Medium)
        .alignSelf(ItemAlign.Start)
      Flex({ wrap: FlexWrap.Wrap, space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) } }) {
        Text('蜜雪冰城奶茶')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('coco都可')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('水果捞')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('大润发特价')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('一点点')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('沪上阿姨')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('奶茶')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('塔斯汀')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('华莱士')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
      }
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}

2.3、主轴对齐方式

Flex组件主轴的对齐方式和线性布局容器是很像的,只是ColumnRow组件是在属性里面设置,而Flex组件是在参数里面设置。其余属性名和属性值都是一样的,这里就不重复讲了。

2.4、交叉轴对齐方式

Flex组件交叉轴的对齐方式通过Flex组件的alignItemsalignContent参数来设置,他们两个的区别是alignItems用来设置单行子元素交叉轴对齐方式的,而alignContent可以设置换行情况下多行子元素交叉轴的对齐方式。

alignItems属性的值是一个枚举类型:ItemAlign

名称描述
Auto使用Flex容器中默认配置。
Start元素在Flex容器中,交叉轴方向首部对齐。
Center元素在Flex容器中,交叉轴方向居中对齐。
End元素在Flex容器中,交叉轴方向底部对齐。
Stretch元素在Flex容器中,交叉轴方向拉伸填充。容器为Flex且设置Wrap为FlexWrap.Wrap或FlexWrap.WrapReverse时,元素拉伸到与当前行/列交叉轴长度最长的元素尺寸。其余情况下,无论元素尺寸是否设置,均拉伸到容器尺寸。
Baseline元素在Flex容器中,交叉轴方向文本基线对齐。

alignContent属性的值是一个枚举类型:FlexAlign,属性名和属性值和线性布局是一样的。

alignItems属性里面的StartCenterEnd没什么可说的,前面都已经见过了。这里我们来演示一下其他的几种。

我们先通过一个例子看一下效果

@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 }) {
      Text('ItemAlign.Auto')
        .fontWeight(FontWeight.Medium)
        .alignSelf(ItemAlign.Start)
      Flex({ alignItems: ItemAlign.Auto }) {
        Text('蜜雪冰城奶茶')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('coco都可')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('水果捞')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
      }
      .height(100)
      .backgroundColor(Color.Orange)
      Text('ItemAlign.Baseline')
        .fontWeight(FontWeight.Medium)
        .alignSelf(ItemAlign.Start)
      Flex({ alignItems: ItemAlign.Baseline }) {
        Text('蜜雪冰城奶茶')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('coco都可')
          .fontSize(18)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('水果捞')
          .fontSize(22)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
      }
      .height(100)
      .backgroundColor(Color.Blue)
      Text('ItemAlign.Stretch')
        .fontWeight(FontWeight.Medium)
        .alignSelf(ItemAlign.Start)
      Flex({ alignItems: ItemAlign.Stretch }) {
        Text('蜜雪冰城奶茶')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('coco都可')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
        Text('水果捞')
          .fontSize(14)
          .padding(10)
          .backgroundColor('#f5f5f5')
          .borderRadius(20)
      }
      .height(100)
      .backgroundColor(Color.Pink)
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}

image.png

从这我们能看出来默认的ItemAlign.Auto的效果其实和ItemAlign.Top是一样的,ItemAlign.Baseline的效果是不管容器的大小都按着文字的基线来对齐,而ItemAlign.Stretch的效果是拉伸侧轴的高度至填满容器。

到这可能有的小伙伴会认为,Flex容器这么好用,可以实现换行还可以随便调整主轴,那我以后一直用这个容器就好了呀。其实不然,如果以后在开发布局过程中,能使用线性布局尽量用线性布局。因为其实Column和Row组件就是在Flex容器的基础上开发的,做了对应的优化,所以以后为了性能方便考虑,优先使用Column和Row容器。

3、层叠布局

层叠布局也叫做堆叠布局,由Stack组件实现。通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。

image.png

我们还用这个例子来讲解,我们再换一种思路。我们将左边这块看作是一整个容器,宽度占据100%,而右边的按钮直接在右边压在它上面。但是默认情况下在Stack容器里,子元素都是居中层叠的,如果我们想改变层叠位置,可以通过设置参数alignContent: Alignment.End来实现。

@Entry
@Component
struct Index {
  build() {
    Column() {
      Stack({ alignContent: Alignment.End }) {
        Row({ space: 10 }) {
          Text('知乎')
            .width(40)
            .height(40)
            .fontColor(Color.White)
            .textAlign(TextAlign.Center)
            .borderRadius(7)
            .backgroundColor('#0077ff')
          Column({ space: 5 }) {
            Text('知乎')
            Text('有问题就会有答案')
              .fontSize(12)
              .fontColor('#999')
          }
          .alignItems(HorizontalAlign.Start)
        }
        .width('100%')
        Text('打开App')
          .width(90)
          .height(35)
          .backgroundColor('#0077ff')
          .textAlign(TextAlign.Center)
          .fontColor(Color.White)
          .borderRadius(20)
      }
      .width('100%')
      .height(70)
      .padding(10)
    }
    .width('100%')
    .height('100%')
  }
}

3.1、对齐方式

Stack组件有一个参数为alignContent,它的值为一个枚举:Alignment

名称描述
TopStart顶部起始端。
Top顶部横向居中。
TopEnd顶部尾端。
Start起始端纵向居中。
Center横向和纵向居中。
End尾端纵向居中。
BottomStart底部起始端。
Bottom底部横向居中。
BottomEnd底部尾端。

我们可以通过这个参数来设置容器内所有子元素的对齐方式,也可以给子元素设置相对定位或者绝对定位来调整位置。

3.2、Z序控制

@Entry
@Component
struct Index {
  build() {
    Column() {
      Stack() {
        Text()
          .width(100)
          .height(100)
          .backgroundColor(Color.Orange)
          .zIndex(2)
        Text()
          .width(200)
          .height(200)
          .backgroundColor(Color.Blue)
      }
      .width('100%')
      .height(300)
      .backgroundColor(Color.Pink)
    }
    .width('100%')
    .height('100%')
  }
}

我们先来观察一下这段代码,一个Stack容器包裹着两个Text组件,一个的宽高为100,另一个宽高为200。按照容器的特性,后面的容器应该覆盖前面的容器,但实际情况却是这样的。

image.png

这是因为组件都有一个通用的属性:zIndex,这个属性可以改变组件的显示层级,通过这个属性我们可以完成一些特殊的效果。

4、网格布局

网格布局,在视频平台或相册中等比较常用,网格布局通过Grid容器实现。这个容器通过行和列分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。这个容器有一个限定,就是子容器使用GridItem,子容器里面可以放我们想要的东西。

image.png

4.1、设置网格布局行列数量及其占比

像上面的图片就属于一个两行两列的网格布局,如果我们想实现一个两行两列的布局就需要使用Grid容器的rowsTemplate属性来设置行数量,使用columnsTemplate属性来设置列数量。

这两个属性的使用方法是一样的,属性值是一个字符串,格式是'1fr 1fr...',之间用空格间隔,你想要几行或者几列就写几个fr。而那个数字1就是占比,比如你需要一个两行两列的布局,同时第一列GridItem在这一行中占比2/3,那你就需要设置columnsTemplate('2fr 1fr'),具体代码效果如下:

@Entry
@Component
struct Index {
  build() {
    Column() {
      Grid() {
        GridItem() {
          Text()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Orange)
        }
        GridItem() {
          Text()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Blue)
        }
        GridItem() {
          Text()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Green)
        }
        GridItem() {
          Text()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Yellow)
        }
      }
      .width('100%')
      .height(300)
      .backgroundColor(Color.Pink)
      .padding(10)
      .columnsTemplate('2fr 1fr')
      .rowsTemplate('1fr 1fr')
    }
    .width('100%')
    .height('100%')
  }
}

image.png

总结

上述说的这些都是日常开发中比较常用的布局和相关实现组件的参数属性,能理解并熟练运用会对你的布局有很大帮助。

如果我写的文章对你有帮助,欢迎给我留言,我会很高兴。如果有好的建议也欢迎提给我,后续我会继续编写更多相关技术文章和我学习过程中遇到的各种坑分享给大家,欢迎大家关注我~