一个页面由很多组件组成,如果需要把这些组件组织起来布局好,需要借助容器组件来实现。
容器组件是一种特殊的组件,它可以包含其他组件,而且按照一定的规律布局,一个容器组件中可以放置基础组件,也可以放置容器组件,通过多层布局的嵌套,可以布局出更丰富的页面。
线性布局容器表示按照垂直方向或水平方向排列子组件的容器,ArkTS中使用Column和Row来实现线性布局
Column:垂直方向的布局容器
Row:水平方向的布局容器
主轴与交叉轴是默认存在的两个轴,它们是相互垂直的。在不同的容器中主轴的方向不一样。
主轴
在Column容器中按从上到下垂直方向布局,主轴是垂直方向
在Row容器中按从左到右水平方向布局,主轴是水平方向
交叉轴
与主轴垂直相交的轴
组件在主轴与交叉轴上的排列方式
Column和Row容器有丙个属性justifyContent,alignItems
justifyContent:在主轴上的对齐方式
其设置的参数是FlexAlign,它有如下几个可选值
alignItem:在交叉轴上的对齐方式
Column容器设置的参数类型HorizontalAlign,它有如上几种可选值
Row容器设置的参数类型是VerticalAlign,它有如下几种可选值
容器组件 | 接口 |
Column | Column(value?:{space?:string | number}) |
Row | Row(value?:{space?:string | number}) |
接口中有一个可选参数space表示子组件在主轴上的间距
实现如下页面:
@Entry
@Component
struct Login {
build() {
Column(){
// 图片
Image($r('app.media.icon'))
.width(60)
.height(60)
.margin({bottom:-45})
// appname
Text($r('app.string.app_name'))
// 提示语
Text('登录账号获取更多服务')
.fontColor(Color.Gray)
// 账号,密码输入区
TextInput({placeholder: '请输入账号'})
.width('80%')
TextInput({placeholder: '请输入密码'})
.width('80%')
.type(InputType.Password)
// 两个链接信息放在一行上
Row(){
Text('短信验证登录')
.fontColor(Color.Blue)
Text('忘记密码')
.fontColor(Color.Blue)
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
// 登录按钮
Button($r('app.string.login_text'),{type: ButtonType.Capsule, stateEffect: true})
.width('70%')
// 注册账号
Text('注册账号')
.fontColor(Color.Blue)
Text('其它方式登录')
.fontColor(Color.Gray)
.margin({bottom:-45})
// 多种方式登录放在一行
Row(){
Button('方式一',{type: ButtonType.Circle,stateEffect: true})
.width('20%')
.fontColor(Color.White)
.backgroundColor(Color.Gray)
Button('方式二',{type: ButtonType.Circle,stateEffect: true})
.width('20%')
.fontColor(Color.White)
.backgroundColor(Color.Gray)
Button('方式三',{type: ButtonType.Circle,stateEffect: true})
.width('20%')
.fontColor(Color.White)
.backgroundColor(Color.Gray)
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.alignItems(HorizontalAlign.Center) // 默认就是这种对齐方式可以不用配置
}
}
在手机应用中通常会看到一些数据列表,组成这些列表的常用布局就是使用网络布局和列表布局。
它是最常用的滚动类容器组件,一般和子组件ListItem一起使用,List列表中的每一个列表项则对应一个ListItem组件。
列表往往由多个列表项组成,在List组件中使用多个ListItem组件来构建列表,但这样操作会导致代码冗余,此时可以使用循环渲染(ForEach)遍历数组的方式来构建。
@Entry
@Component
struct ListTest {
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)
}
}
List组件子组件ListItem之间默认是不会有分隔线的,如果需要在各子组件之间设置分隔线需要在List组件上使用divider属性,属性包含以下四个参数
注意:设置这个之后会在各个ListItem组件之间加上一个分隔线,最后一个ListItem后不会加。
List组件提供了一系列事件方法用来监听列表的滚动,可根据需求监听这些事件来做一些操作
@Entry
@Component
struct ListTest {
private arr: number[] = [0,1,2,3,4,5,6,7,8,9];
// 滑动起始索引与滑动结束过引
@State firstIndex: number = -1;
@State lastIndex: number = -1;
// 滑动偏移量与状态
@State scrollOffset: number = -1;
@State scrollState: ScrollState = undefined;
build() {
Column() {
Row(){
Text(`滑动起始索引:${this.firstIndex === -1? '' : this.firstIndex}`)
Text(`滑动结束索引:${this.lastIndex === -1? '' : this.lastIndex}`)
}.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
Row(){
Text(`滑动偏移量:${this.scrollOffset === -1? '' : this.scrollOffset}`)
Text(`滑动状态:${this.scrollState == undefined? '' : this.scrollState.toString()}`)
}.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
List({space: 10}){ // 这里{space: 10}设置了每个ListItem之间的距离
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)
}
.divider({strokeWidth: 2,color: Color.Gray, startMargin: 10, endMargin: 10})
// 滑动相关事件
.onScrollIndex((firstIndex: number, lastIndex: number) => {
this.firstIndex = -1;
this.lastIndex = -1;
this.scrollOffset = -1;
this.scrollState = undefined;
this.firstIndex = firstIndex;
this.lastIndex = lastIndex;
})
.onScroll((scrollOffset: number, scrollState: ScrollState) =>{
this.scrollOffset = Number(scrollOffset.toFixed(2))
this.scrollState = scrollState;
})
.onReachStart(() => {
console.info('触发List组件的onReachStart事件');
})
.onReachEnd(() => {
console.info('触发List组件的onReachEnd事件');
})
.onScrollStop(() => {
console.info('触发List组件的onScrollStop事件');
})
}
.padding(12)
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
在List组件中的列表默认是按垂直方向排列的。
如果我们需要改变这个默认的排列方向可以把List组件的listDirection属性设置为Axis.Horizontal
listDirection的参数类型是Axis,它支持两种选项
Grid组件又称为网络容器,是一种网络列表,由“行”与“列”分隔单元格所组成。
Grid组件一般与子组件GridItem一起使用,其中的每一个条目则对应一个GridItem组件。
@Entry
@Component
struct GridTest {
// 定义一个数组
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)
}
}
上面创建了16个GridItem列表项。
columnsTemplate的值设置为'1fr 1fr 1fr 1fr',表示网络是一个四列网络
rowsTemplate的值为’1fr 1fr 1fr 1fr‘,表示这个网格为四行网络
columnsGap设置列间距为10vp,rowsGap设置行间距为10vp
上面构建的网格布局就是一个4 * 4的网格布局,固定的是行列数,如果我们内容较多,可以通过滚动方式来显示更多内容,则需要一个可以滚动的网络布局。只需要设置rowsTemplate和columnsTemplate中的一个即可。
在我们常用的应用中,经常会有视图内容的切换需求。比如底部有一个商城、购物车两个Tab标签页面,分别点击进入商城和购物车页面。
ArkUI开发框架提供了一种页签容器组件Tabs,使用这个组件可以很容易实现内容视图的切换。
页签容器Tabs的形式多种多样,不同的页面设计页签不一样,可以把页签放在底部、顶部或者侧边
@Entry
@Component
struct TabsTest {
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%')
}
}
Tabs组件包含多个TabContent,通过TabContent的tabBar属性设置TabBar的显示内容。
Tabs组件中使用width和height设置Tabs组件的宽高,而barWidth和barHeight设置TabBar的宽度和高度。
Tabs默认是Fixed的,所以默认情况下Tabs页签是不可滑动的。在这种模式下如果页签比较多则可能会导致页签显示不全。如果把布局模式设置为Scrollable则可以实现页签的滚动。
Tabs的布局模式有如现两种
@Entry
@Component
struct TabsTest {
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') // 标签名称
}
.barMode(BarMode.Scrollable) // 设置布局模式,这里默认是BarMode.Fixed
.barWidth('100%') // TabBar宽度
.barHeight(60) // TabBar高度
.width('100%') // Tabs组件宽度
.height('100%') // Tabs组件的高度
.backgroundColor(0xF5F5F5) // Tabs组件背景颜色
}
// .width('100%')
}
}
Tabs组件页签默认显示在顶部,可以使用Tabs组件接口中参数barPosititon设置标签位置。
页签显示位置还与vertical属性相关联,vertical属性用于设置页面的排列方向,当vertical的属性值为false(默认值)时页签横向排列,当为true时表示纵向排列
@Entry
@Component
struct TabsTest {
private controller: TabsController = new TabsController();
build() {
Column() {
Tabs({barPosition: /*BarPosition.Start*/ BarPosition.End, 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') // 标签名称
}
.vertical(true)
// .barMode(BarMode.Scrollable) // 设置布局模式,这里默认是BarMode.Fixed
.barWidth(100) // TabBar宽度
.barHeight(200) // TabBar高度
.width('100%') // Tabs组件宽度
.height('100%') // Tabs组件的高度
.backgroundColor(0xF5F5F5) // Tabs组件背景颜色
}
// .width('100%')
}
}
Tabs组件接口中参数barPosititon加上他的属性vertical组合则可以让Tabs定位到顶部 底部 左侧 右侧。
@Entry
@Component
struct TabsTest2 {
@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: 32,height:32})
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.index2'),$r('app.media.index1')))
TabContent(){
Column().width('100%').height('100%').backgroundColor('#007DFF')
}
.tabBar(this.TabBuilder('我的',1,$r('app.media.my2'),$r('app.media.my1')))
}
.barWidth('100%')
.barHeight(50)
.onChange((index: number) => {
this.currentIndex = index;
})
}
}