鸿蒙开发笔记-20-ArkUI:栅格布局, 列表,网格,轮播,,选项卡
ArkUI作为鸿蒙系统的UI框架,提供了五种常用布局组件:栅格布局(GridRow/GridCol)、列表(List)、网格(Grid)、轮播和选项卡。栅格布局通过行列划分实现自适应多设备布局;列表组件适合展示结构化数据,支持分组、懒加载等高级功能;网格布局提供强大的二维布局能力。本文详细介绍了这三种布局的核心优势、使用方法和代码示例,帮助开发者快速掌握ArkUI的基础布局技术,为构建响应式界面奠
ArkUI作为鸿蒙系统的UI框架,提供了丰富的布局组件,帮助开发者快速构建美观且响应式的界面。本文将介绍ArkUI中最常用的五种布局组件:栅格布局、列表、网格、轮播和选项卡,适合初学者循序渐进地学习和掌握。
一、栅格布局 (GridRow/GridCol)
栅格布局是一种通用的辅助定位工具,特别适合在不同尺寸设备上实现自适应布局。它通过将页面划分为等宽的列和行,为界面元素提供统一的定位标准。
核心优势
- 规律性定位:通过行列划分,使界面元素排列有序
- 多设备适配:统一的定位标注确保不同设备上布局一致性
- 灵活间距调整:支持行列间距的精细控制
- 自动换行与自适应:内容超出时自动换行,适应不同屏幕尺寸
| 功能 | 说明 | 示例 |
|---|---|---|
| 断点系统 | 响应不同屏幕尺寸 | breakpoints: {value: ['320vp', '520vp']} |
| 自定义列数 | 灵活控制布局结构 | columns: { md: 8, lg: 12 } |
| 元素排序 | 控制显示顺序 | order: { xs: 2, sm: 1 } |
| 嵌套布局 | 构建复杂界面 | GridRow内嵌套GridRow |
基本使用
栅格布局由GridRow(栅格容器)和GridCol(栅格子组件)组成:
GridRow() {
GridCol() {
Text('第一列内容')
.width('100%')
.height(80)
.backgroundColor('#FF4081')
}
GridCol() {
Text('第二列内容')
.width('100%')
.height(80)
.backgroundColor('#3F51B5')
}
}
.columns(2) // 设置总列数为2
.gutter(10) // 设置列间距为10vp
断点
栅格系统默认将设备宽度分为四类断点,可根据不同屏幕尺寸调整布局:
// 自定义断点
GridRow({
breakpoints: { value: ['320vp', '520vp', '840vp', '1080vp'] }
}) {
GridCol({
span: { xs: 12, sm: 6, md: 4, lg: 3 }
}) {
// 子组件内容
}
}
上述代码表示:在最小屏幕(xs)上占12列(全屏),小屏(sm)占6列(半屏),中屏(md)占4列,大屏(lg)占3列。
示例
排列方向控制
// 默认从左到右排列
GridRow({ direction: GridRowDirection.Row }) { ... }
// 从右到左排列
GridRow({ direction: GridRowDirection.RowReverse }) { ... }
子组件偏移与排序
// 偏移2列
GridCol({ offset: 2 }) { ... }
// 自定义排序
GridCol({ order: { xs: 3, sm: 2, md: 1 } }) { ... }
嵌套使用
栅格布局支持嵌套,实现复杂界面:
GridRow() {
GridCol({ span: 8 }) {
// 左侧主内容区
GridRow() {
GridCol({ span: 12 }) { /* 标题区 */ }
GridCol({ span: 6 }) { /* 左侧内容 */ }
GridCol({ span: 6 }) { /* 右侧内容 */ }
}
}
GridCol({ span: 4 }) {
// 右侧边栏
}
}
二、列表 (List)
- 列表是用于展示结构化、可滚动信息的高效组件,适合呈现同类数据集合。
| 功能 | 实现方式 | 应用场景 |
|---|---|---|
| 分组列表 | ListItemGroup | 联系人分组 |
| 粘性标题 | sticky: StickyStyle.Header | 分类列表 |
| 侧滑操作 | swipeAction | 消息删除 |
| 懒加载 | LazyForEach | 长列表优化 |
基本使用
List() {
ListItem() {
Text('列表项1')
.width('100%')
.height(60)
.padding(16)
}
ListItem() {
Text('列表项2')
.width('100%')
.height(60)
.padding(16)
}
}
.width('100%')
.divider({ strokeWidth: 2, color: '#EEEEEE' }) // 添加分隔线
循环渲染列表
使用ForEach实现动态列表:
@Entry
@Component
struct ListDemo {
private items: string[] = ['苹果', '香蕉', '橙子', '草莓', '葡萄']
build() {
List() {
ForEach(this.items, (item) => {
ListItem() {
Text(item)
.width('100%')
.height(60)
.padding(16)
}
}, (item) => item)
}
.width('100%')
.space(8) // 列表项间距
}
}
列表分类与分组
使用ListItemGroup实现分组列表:
List() {
ListItemGroup({ header: Text('水果').fontSize(16).fontWeight(FontWeight.Bold) }) {
ListItem() { Text('苹果') }
ListItem() { Text('香蕉') }
}
ListItemGroup({ header: Text('蔬菜').fontSize(16).fontWeight(FontWeight.Bold) }) {
ListItem() { Text('西红柿') }
ListItem() { Text('黄瓜') }
}
}
.sticky(StickyStyle.Header) // 粘性标题(吸顶效果)
长列表懒加载
对于大量数据,使用LazyForEach实现懒加载:
class MyDataSource implements IDataSource {
private data: string[] = []
constructor(size: number) {
// 模拟1000条数据
for (let i = 0; i < size; i++) {
this.data.push(`列表项 ${i + 1}`);
}
}
totalCount(): number {
return this.data.length;
}
getData(index: number): string {
return this.data[index];
}
registerDataChangeListener(listener: DataChangeListener): void {}
unregisterDataChangeListener(listener: DataChangeListener): void {}
}
@Entry
@Component
struct LazyListDemo {
private dataSource: MyDataSource = new MyDataSource(1000);
build() {
List() {
LazyForEach(this.dataSource, (item: string) => {
ListItem() {
Text(item)
.width('100%')
.height(60)
.padding(16)
}
})
}
.cachedCount(5) // 预加载5项
}
}
滑动删除
ListItem() {
Text('可滑动删除项')
}
.swipeAction({
end: this.deleteButton()
})
@Builder deleteButton() {
Button('删除')
.width(60)
.height(60)
.backgroundColor(Color.Red)
.onClick(() => {
// 处理删除逻辑
})
}
下拉刷新与上拉加载
List() {
// 列表内容
}
.onReachStart(() => {
// 下拉刷新逻辑
})
.onReachEnd(() => {
// 上拉加载更多逻辑
})
三、网格布局 (Grid/GridItem)
网格布局提供了强大的二维布局能力,适合实现九宫格、计算器、日历等界面。
基本使用
Grid() {
GridItem() { Text('1').fontSize(24) }
GridItem() { Text('2').fontSize(24) }
GridItem() { Text('3').fontSize(24) }
GridItem() { Text('4').fontSize(24) }
GridItem() { Text('5').fontSize(24) }
GridItem() { Text('6').fontSize(24) }
}
.columnsTemplate('1fr 1fr 1fr') // 3列等宽
.rowsTemplate('1fr 1fr') // 2行等高
.columnsGap(10) // 列间距
.rowsGap(10) // 行间距
.width('100%')
.height(300)
不规则网格布局
通过onGetRectByIndex实现跨行跨列效果:
Grid({
layoutOptions: {
regularSize: [1, 1], // 常规项占1行1列
onGetRectByIndex: (index: number) => {
// 自定义特定项的位置和大小
if (index === 0) {
return [0, 0, 2, 2]; // [rowStart, columnStart, rowSpan, columnSpan]
}
return [0, 0, 1, 1]; // 默认占1行1列
}
}
}) {
// 第一个项跨2行2列
GridItem() {
Text('大项')
.width('100%')
.height('100%')
.backgroundColor('#FF4081')
}
// 其他常规项
ForEach(Array(8).fill(0), (_, i) => {
GridItem() {
Text(`${i + 2}`)
.width('100%')
.height('100%')
.backgroundColor('#3F51B5')
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height(400)
响应式网格
根据屏幕尺寸动态调整列数:
@Entry
@Component
struct ResponsiveGrid {
@State columns: string = '1fr' // 默认1列
// 监听屏幕尺寸变化
private mediaQueryListener: MediaQueryListener = mediaquery.matchMediaSync('(min-width: 600vp)');
aboutToAppear() {
this.mediaQueryListener.on('change', (result) => {
if (result.matches) {
this.columns = '1fr 1fr'; // 大屏显示2列
} else {
this.columns = '1fr'; // 小屏显示1列
}
});
}
build() {
Grid() {
// 网格内容
ForEach(Array(6).fill(0), (_, i) => {
GridItem() {
Text(`Item ${i + 1}`)
.width('100%')
.height(100)
.backgroundColor('#3F51B5')
}
})
}
.columnsTemplate(this.columns)
.columnsGap(10)
.rowsGap(10)
.padding(10)
}
}
示例:计算器界面
Grid() {
GridItem() { Text('C').fontSize(24) }
.backgroundColor('#9E9E9E')
GridItem() { Text('←').fontSize(24) }
.backgroundColor('#9E9E9E')
GridItem() { Text('%').fontSize(24) }
.backgroundColor('#9E9E9E')
GridItem() { Text('÷').fontSize(24) }
.backgroundColor('#FF9800')
// 数字按钮...
// 0按钮跨2列
GridItem({ columnStart: 0, columnEnd: 2 }) {
Text('0').fontSize(24)
}
.backgroundColor('#333333')
GridItem() { Text('.').fontSize(24) }
.backgroundColor('#333333')
GridItem() { Text('=').fontSize(24) }
.backgroundColor('#FF9800')
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.width('100%')
.height(500)
四、轮播组件 (Swiper)
轮播组件用于实现内容的滑动切换,常见于广告轮播、图片预览等场景。
基本使用
Swiper() {
Text('第一页')
.width('100%')
.height(200)
.backgroundColor('#FF4081')
.textAlign(TextAlign.Center)
.fontSize(24)
Text('第二页')
.width('100%')
.height(200)
.backgroundColor('#3F51B5')
.textAlign(TextAlign.Center)
.fontSize(24)
Text('第三页')
.width('100%')
.height(200)
.backgroundColor('#4CAF50')
.textAlign(TextAlign.Center)
.fontSize(24)
}
.indicator(true) // 显示指示器
.autoPlay(true) // 自动播放
.interval(3000) // 切换间隔3秒
.loop(true) // 循环播放
图片轮播实现
@Entry
@Component
struct ImageSwiper {
private images: Resource[] = [
$r('app.media.image1'),
$r('app.media.image2'),
$r('app.media.image3')
]
build() {
Swiper() {
ForEach(this.images, (image) => {
Image(image)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
})
}
.indicator(Indicator.Dot().selectedColor(Color.Red))
.autoPlay(true)
.loop(true)
}
}
自定义指示器样式
Swiper() {
// 轮播内容
}
.indicator(
Indicator.Number()
.fontColor(Color.White)
.selectedFontColor(Color.Red)
.fontSize(14)
)
垂直轮播
Swiper() {
// 轮播内容
}
.vertical(true) // 垂直方向轮播
.height(300)
控制器控制
@Entry
@Component
struct ControlledSwiper {
private swiperController: SwiperController = new SwiperController()
build() {
Column() {
Swiper(this.swiperController) {
// 轮播内容
}
.indicator(true)
Row({ space: 10 }) {
Button('上一页')
.onClick(() => {
this.swiperController.showPrevious()
})
Button('下一页')
.onClick(() => {
this.swiperController.showNext()
})
}
.margin(10)
}
}
}
五、选项卡 (Tabs)
选项卡组件用于在一个页面内实现多个视图的快速切换,常见于分类内容展示。
基本使用
Tabs() {
TabContent() {
Text('首页内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
.fontSize(24)
}
.tabBar('首页')
TabContent() {
Text('发现内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
.fontSize(24)
}
.tabBar('发现')
TabContent() {
Text('我的内容')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
.fontSize(24)
}
.tabBar('我的')
}
.width('100%')
.height(300)
底部导航栏
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
// 首页内容
}
.tabBar(this.tabBuilder('首页', $r('app.media.home')))
TabContent() {
// 分类内容
}
.tabBar(this.tabBuilder('分类', $r('app.media.category')))
TabContent() {
// 我的内容
}
.tabBar(this.tabBuilder('我的', $r('app.media.mine')))
}
.barHeight(56) // 底部导航栏高度
.backgroundColor('#FFFFFF') // 背景色
@Builder tabBuilder(title: string, icon: Resource) {
Column({ space: 4 }) {
Image(icon)
.size({ width: 24, height: 24 })
Text(title)
.fontSize(12)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
控制器与事件监听
@Entry
@Component
struct TabsWithController {
private controller: TabsController = new TabsController()
@State currentIndex: number = 0
build() {
Column() {
Tabs({
barPosition: BarPosition.Start,
controller: this.controller,
index: this.currentIndex
}) {
TabContent() { Text('页面1') }.tabBar('标签1')
TabContent() { Text('页面2') }.tabBar('标签2')
TabContent() { Text('页面3') }.tabBar('标签3')
}
.onChange((index: number) => {
this.currentIndex = index
console.log(`切换到标签 ${index + 1}`)
})
Button('切换到标签2')
.margin(10)
.onClick(() => {
this.controller.changeIndex(1)
})
}
}
}
自定义选项卡样式
Tabs() {
// 选项卡内容
}
.barMode(BarMode.Fixed) // 固定模式
.barWidth(300) // 选项卡宽度
.animationDuration(300) // 切换动画时长
六、总结
布局选择原则:
- 单列数据:List
- 二维网格:Grid
- 跨设备响应:栅格+媒体查询
- 导航结构:Tabs
性能优化:
- 长列表使用LazyForEach
- 设置合理cachedCount
- 避免深层次嵌套
我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章
更多推荐



所有评论(0)