八、Column&Row组件的使用
概述
一个丰富的页面需要很多组件组成,那么,我们如何才能让这些组件有条不紊地在页面上布局呢?这就需要借助容器组件来实现。
容器组件是一种比较特殊的组件,它可以包含其他的组件,而且按照一定的规律布局,帮助开发者生成精美的页面。容器组件除了放置基础组件外,也可以放置容器组件,通过多层布局的嵌套,可以布局出更丰富的页面。
ArkTS
为我们提供了丰富的容器组件来布局页面,本文将以构建登录页面为例,介绍Column
和Row
组件的属性与使用。
1、组件介绍
1.1、布局容器概念
线性布局容器表示按照垂直方向或者水平方向排列子组件的容器,ArkTS
提供了Column
和Row
容器来实现线性布局。
Column
表示沿垂直方向布局的容器。Row
表示沿水平方向布局的容器。
1.2、主轴和交叉轴概念
在布局容器中,默认存在两根轴,分别是主轴和交叉轴,这两个轴始终是相互垂直的。不同的容器中主轴的方向不一样的。
- 主轴: 在
Column
容器中的子组件是按照从上到下的垂直方向布局的,其主轴的方向是垂直方向;在Row
容器中的组件是按照从左到右的水平方向布局的,其主轴的方向是水平方向。
- 交叉轴: 与主轴垂直相交的轴线,如果主轴是垂直方向,则交叉轴就是水平方向;如果主轴是水平方向,则交叉轴是垂直方向。
1.3、属性介绍
了解布局容器的主轴和交叉轴,主要是为了让大家更好地理解子组件在主轴和交叉轴的排列方式。
接下来,我们将详细讲解Column
和Row
容器的两个属性justifyContent
和alignItems
。
属性名称 | 描述 |
---|---|
justifyContent | 设置子组件在主轴方向上的对齐格式。 |
alignItems | 设置子组件在交叉轴方向上的对齐格式。 |
1. 主轴方向的对齐(justifyContent)
子组件在主轴方向上的对齐使用justifyContent
属性来设置,其参数类型是FlexAlign
。FlexAlign
定义了以下几种类型:
- Start: 元素在主轴方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。
- Center: 元素在主轴方向中心对齐,第一个元素与行首的距离以及最后一个元素与行尾距离相同。
- End: 元素在主轴方向尾部对齐,最后一个元素与行尾对齐,其他元素与后一个对齐。
- SpaceBetween: 元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素与行首对齐,最后一个元素与行尾对齐。
- **SpaceAround:**元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
- SpaceEvenly: 元素在主轴方向等间距布局,无论是相邻元素还是边界元素到容器的间距都一样。
2. 交叉轴方向的对齐(alignItems)
子组件在交叉轴方向上的对齐方式使用alignItems
属性来设置。
Column
容器的主轴是垂直方向,交叉轴是水平方向,其参数类型为HorizontalAlign
(水平对齐),HorizontalAlign
定义了以下几种类型:
- Start: 设置子组件在水平方向上按照起始端对齐。
- **Center(默认值):**设置子组件在水平方向上居中对齐。
- End: 设置子组件在水平方向上按照末端对齐。
Row
容器的主轴是水平方向,交叉轴是垂直方向,其参数类型为VerticalAlign
(垂直对齐),VerticalAlign
定义了以下几种类型: - Top: 设置子组件在垂直方向上居顶部对齐。
- Center(默认值): 设置子组件在竖直方向上居中对齐。
- **Bottom:**设置子组件在竖直方向上居底部对齐。
1.4、接口介绍
接下来,我们介绍Column
和Row
容器的接口。
容器组件 | 接口 |
---|---|
Column | Column(value?:{space?: string | number}) |
Row | Row(value?:{space?: string| number}) |
Column
和Row
容器的接口都有一个可选参数space
,表示子组件在主轴方向上的间距。效果如下:
2、组件使用
我们来具体讲解如何高效的使用Column
和Row
容器组件来构建这个登录页面。
当我们从设计同学那拿到一个页面设计图时,我们需要对页面进行拆解,先确定页面的布局,再分析页面上的内容分别使用哪些组件来实现。
我们仔细分析这个登录页面。在静态布局中,组件整体是从上到下布局的,因此构建该页面可以使用Column
来构建。在此基础上,我们可以看到有部分内容在水平方向上由几个基础组件构成,例如页面中间的短信验证码登录与忘记密码以及页面最下方的其他方式登录,那么构建这些内容的时候,可以在Column
组件中嵌套Row
组件,继而在Row
组件中实现水平方向的布局。
根据上述页面拆解,在Column
容器里,依次是Image、Text、TextInput、Button
等基础组件,还有两组组件是使用Row
容器组件来实现的,主要代码如下:
@Entry
@Component
export struct LoginPage {
build() {
Column() {
Image($r('app.media.logo'))
...
Text($r('app.string.login_page'))
...
Text($r('app.string.login_more'))
...
TextInput({ placeholder: $r('app.string.account') })
...
TextInput({ placeholder: $r('app.string.password') })
...
Row() {
Text($r(…))
Text($r(…))
}
Button($r('app.string.login'), { type: ButtonType.Capsule, stateEffect: true })
...
Row() {
this.imageButton($r(…))
this.imageButton($r(…))
this.imageButton($r(…))
}
...
}
...
}
}
我们详细看一下使用Row
容器的两组组件。
两个文本组件展示的内容是按水平方向布局的,使用两端对齐的方式。这里我们使用Row
容器组件,并且需要配置主轴上(水平方向)的对齐格式justifyContent
为FlexAlign.SpaceBetween
(两端对齐)。
Row() {
Text($r(…))
Text($r(…))
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
其他登录方式的三个按钮也是按水平方向布局的,同样使用Row
容器组件。这里按钮的间距是一致的,我们可以通过配置可选参数space
来设置按钮间距,使子组件间距一致。
Row({ space: CommonConstants.LOGIN_METHODS_SPACE }) {
this.imageButton($r(…))
this.imageButton($r(…))
this.imageButton($r(…))
}
九、List组件和Grid组件的使用
1、简介
在我们常用的手机应用中,经常会见到一些数据列表,如设置页面、通讯录、商品列表等。下图中两个页面都包含列表,“首页”页面中包含两个网格布局,“商城”页面中包含一个商品列表。
上图中的列表中都包含一系列相同宽度的列表项,连续、多行呈现同类数据,例如图片和文本。常见的列表有线性列表(List列表)和网格布局(Grid列表):
为了帮助开发者构建包含列表的应用,ArkUI提供了List组件和Grid组件,开发者使用List和Grid组件能够很轻松的完成一些列表页面。
2、List组件的使用
2.1、List组件简介
List是很常用的滚动类容器组件,一般和子组件ListItem一起使用,List列表中的每一个列表项对应一个ListItem组件。
2.2、使用ForEeach渲染列表
列表往往由多个列表项组成,所以我们需要在List
组件中使用多个ListItem
组件来构建列表,这就会导致代码的冗余。使用循环渲染(ForEach
)遍历数组的方式构建列表,可以减少重复代码,示例代码如下:
@Entry
@Component
struct ListDemo {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
build() {
Column() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`${item}`)
.width('100%')
.height(100)
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0x007DFF)
}
}, item => item)
}
}
.padding(12)
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
效果图如下:
2.3、设置列表分割线
List组件子组件ListItem之间默认是没有分割线的,部分场景子组件ListItem间需要设置分割线,这时候您可以使用List组件的divider属性。divider属性包含四个参数:
- strokeWidth: 分割线的线宽。
- color: 分割线的颜色。
- startMargin: 分割线距离列表侧边起始端的距离。
- endMargin: 分割线距离列表侧边结束端的距离。
2.4、List列表滚动事件监听
List组件提供了一系列事件方法用来监听列表的滚动,您可以根据需要,监听这些事件来做一些操作:
- onScroll: 列表滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态。
- onScrollIndex: 列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
- onReachStart: 列表到达起始位置时触发。
- onReachEnd: 列表到底末尾位置时触发。
- onScrollStop 列表滑动停止时触发。
使用示例代码如下:
List({ space: 10 }) {
ForEach(this.arr, (item) => {
ListItem() {
Text(`${item}`)
...
}
}, item => item)
}
.onScrollIndex((firstIndex: number, lastIndex: number) => {
console.info('first' + firstIndex)
console.info('last' + lastIndex)
})
.onScroll((scrollOffset: number, scrollState: ScrollState) => {
console.info('scrollOffset' + scrollOffset)
console.info('scrollState' + scrollState)
})
.onReachStart(() => {
console.info('onReachStart')
})
.onReachEnd(() => {
console.info('onReachEnd')
})
.onScrollStop(() => {
console.info('onScrollStop')
})
2.5、设置List排列方向
List组件里面的列表项默认是按垂直方向排列的,如果您想让列表沿水平方向排列,您可以将List组件的listDirection属性设置为Axis.Horizontal。listDirection参数类型是Axis,定义了以下两种类型:
-
Vertical(默认值): 子组件ListItem在List容器组件中呈纵向排列。
-
Horizontal: 子组件ListItem在List容器组件中呈横向排列。
3、Grid组件的使用
3.1、Grid组件简介
Grid
组件为网格容器,是一种网格列表,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。Grid
组件一般和子组件GridItem
一起使用,Grid
列表中的每一个条目对应一个GridItem
组件。
3.2、使用ForEach渲染网格布局
和List
组件一样,Grid
组件也可以使用ForEach
来渲染多个列表项GridItem
,我们通过下面的这段示例代码来介绍Grid
组件的使用。
@Entry
@Component
struct GridExample {
// 定义一个长度为16的数组
private arr: string[] = new Array(16).fill('').map((_, index) => `item ${index}`);
build() {
Column() {
Grid() {
ForEach(this.arr, (item: string) => {
GridItem() {
Text(item)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor(0x007DFF)
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.height(300)
}
.width('100%')
.padding(12)
.backgroundColor(0xF1F3F5)
}
}
示例代码中创建了16个GridItem
列表项。同时设置columnsTemplate
的值为’1fr 1fr 1fr 1fr’,表示这个网格为4列,将Grid
允许的宽分为4等分,每列占1份;rowsTemplate
的值为’1fr 1fr 1fr 1fr’,表示这个网格为4行,将Grid
允许的高分为4等分,每行占1份。这样就构成了一个4行4列的网格列表,然后使用columnsGap
设置列间距为10vp,使用rowsGap
设置行间距也为10vp。示例代码效果图如下:
上面构建的网格布局使用了固定的行数和列数,所以构建出的网格是不可滚动的。然而有时候因为内容较多,我们通过滚动的方式来显示更多的内容,就需要一个可以滚动的网格布局。我们只需要设置rowsTemplate
和columnsTemplate
中的一个即可。
将示例代码中GridItem
的高度设置为固定值,例如100;仅设置columnsTemplate
属性,不设置rowsTemplate
属性,就可以实现Grid
列表的滚动:
Grid() {
ForEach(this.arr, (item: string) => {
GridItem() {
Text(item)
.height(100)
...
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.height(300)
此外,Grid
像List
一样也可以使用onScrollIndex
来监听列表的滚动。
四、Tabs组件的使用
1、概述
在我们常用的应用中,经常会有视图内容切换的场景,来展示更加丰富的内容。比如下面这个页面,点击底部的页签的选项,可以实现“首页”和“我的”两个内容视图的切换。
ArkUI
开发框架提供了一种页签容器组件Tabs
,开发者通过Tabs组件可以很容易的实现内容视图的切换。页签容器Tabs
的形式多种多样,不同的页面设计页签不一样,可以把页签设置在底部、顶部或者侧边。
本文将详细介绍Tabs组件的使用。
2、Tabs组件的简单使用
Tabs
组件仅可包含子组件TabContent
,每一个页签对应一个内容视图即TabContent
组件。下面的示例代码构建了一个简单的页签页面:
@Entry
@Component
struct TabsExample {
private controller: TabsController = new TabsController()
build() {
Column() {
Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Green)
}
.tabBar('green')
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Blue)
}
.tabBar('blue')
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Yellow)
}
.tabBar('yellow')
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Pink)
}
.tabBar('pink')
}
.barWidth('100%') // 设置TabBar宽度
.barHeight(60) // 设置TabBar高度
.width('100%') // 设置Tabs组件宽度
.height('100%') // 设置Tabs组件高度
.backgroundColor(0xF5F5F5) // 设置Tabs组件背景颜色
}
.width('100%')
.height('100%')
}
}
效果图如下:
上面示例代码中,Tabs
组件中包含4个子组件TabContent
,通过TabContent
的tabBar
属性设置TabBar
的显示内容。使用通用属性width
和height
设置了Tabs
组件的宽高,使用barWidth
和barHeight
设置了TabBar
的宽度和高度。
说明
- TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
- TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。
3、设置TabBar布局模式
因为Tabs
的布局模式默认是Fixed
的,所以Tabs的页签是不可滑动的。当页签比较多的时候,可能会导致页签显示不全,将布局模式设置为Scrollable
的话,可以实现页签的滚动。
Tabs
的布局模式有Fixed
(默认)和Scrollable
两种:
- BarMode.Fixed: 所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度),页签不可滚动,效果图如下:
- BarMode.Scrollable: 每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。
- 当页签比较多的时候,可以滑动页签,下面的示例代码将
barMode
设置为BarMode.Scrollable
,实现了可滚动的页签:
@Entry
@Component
struct TabsExample {
private controller: TabsController = new TabsController()
build() {
Column() {
Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Green)
}
.tabBar('green')
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Blue)
}
.tabBar('blue')
...
}
.barMode(BarMode.Scrollable)
.barWidth('100%')
.barHeight(60)
.width('100%')
.height('100%')
}
}
}
4、设置TabBar位置和排列方向
Tabs
组件页签默认显示在顶部,某些场景下您可能希望Tabs
页签出现在底部或者侧边,您可以使用Tabs
组件接口中的参数barPosition
设置页签位置。此外页签显示位置还与vertical
属性相关联,vertical
属性用于设置页签的排列方向,当vertical
的属性值为false
(默认值)时页签横向排列,为true
时页签纵向排列。
barPosition
的值可以设置为BarPosition.Start
(默认值)和BarPosition.End
:BarPosition.Start
vertical
属性方法设置为false
(默认值)时,页签位于容器顶部。
Tabs({ barPosition: BarPosition.Start }) {
...
}
.vertical(false)
.barWidth('100%')
.barHeight(60)
效果图如下:
vertical
属性方法设置为true
时,页签位于容器左侧。
Tabs({ barPosition: BarPosition.Start }) {
...
}
.vertical(true)
.barWidth(100)
.barHeight(200)
效果图如下:
BarPosition.End
vertical
属性方法设置为false
时,页签位于容器底部。
Tabs({ barPosition: BarPosition.End }) {
...
}
.vertical(false)
.barWidth('100%')
.barHeight(60)
效果图如下:
vertical
属性方法设置为true时,页签位于容器右侧。
Tabs({ barPosition: BarPosition.End}) {
...
}
.vertical(true)
.barWidth(100)
.barHeight(200)
效果图如下:
5、自定义TabBar样式
TabBar
的默认显示效果如下所示:
往往开发过程中,UX给我们的设计效果可能并不是这样的,比如下面的这种底部页签效果:
TabContent
的tabBar
属性除了支持string
类型,还支持使用@Builder装饰器修饰的函数。您可以使用@Builder
装饰器,构造一个生成自定义TabBar
样式的函数,实现上面的底部页签效果,示例代码如下:
@Entry
@Component
struct TabsExample {
@State currentIndex: number = 0;
private tabsController: TabsController = new TabsController();
@Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 25, height: 25 })
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentIndex = targetIndex;
this.tabsController.changeIndex(this.currentIndex);
})
}
build() {
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
TabContent() {
Column().width('100%').height('100%').backgroundColor('#00CB87')
}
.tabBar(this.TabBuilder('้ฆ–้กต', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))
TabContent() {
Column().width('100%').height('100%').backgroundColor('#007DFF')
}
.tabBar(this.TabBuilder('ๆ‘็', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
}
.barWidth('100%')
.barHeight(50)
.onChange((index: number) => {
this.currentIndex = index;
})
}
}
示例代码中将barPosition
的值设置为BarPosition.End
,使页签显示在底部。使用@Builder
修饰TabBuilder
函数,生成由Image
和Text
组成的页签。同时也给Tabs
组件设置了TabsController
控制器,当点击某个页签时,调用changeIndex
方法进行页签内容切换。
最后还需要给Tabs
添加onChange
事件,Tab
页签切换后触发该事件,这样当我们左右滑动内容视图的时候,页签样式也会跟着改变。
案例代码下载
常用组件与布局