HarmonyOs 应用基础--Swiper-样式结构重用-GridGridItem
目录
九、Swiper-样式&结构重用-Grid&GridItem
1. Swiper
1.1. 适用场景
1.2. 基本用法
1.3. 常用属性方法
1.4. 调整导航点
1.5. 案例-小米有品
2. 样式&结构重用
2.1. @Styles
2.2. @Extend
2.3. @Builder
2.4. @Extend、@Styles、@Builder 对比
3. 网格布局 Grid/GridItem
3.1. 固定行列
3.2. 案例-淘宝二楼
3.3. 合并行列
3.4. 设置滚动
3.5. 案例-小米有品-滚动导航
3.6. 控制器对象-控制滚动
3.7. 控制器对象-自定义滚动条
3.8. 案例-小米有品-滚动导航-自定义滚动条
九、Swiper-样式&结构重用-Grid&GridItem
今日核心:
- 容器组件:Swiper、Grid\GridItem
- 样式&结构重用:@Builder、@Extends、@Styles
1. Swiper
1.1. 适用场景
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示,比如:
1.2. 基本用法
- 轮播内容:内容作为Swiper的子组件即可
- 尺寸:
-
- 设置 Swiper 的尺寸:内容会拉伸为和 Swiper 一致(优先级高)
- 没有设置Swiper的尺寸,Swiper被子组件的尺寸撑开
Swiper() {
// 轮播内容
// (设置尺寸,撑开swiper)
}
// 设置尺寸(内容拉伸、优先级高)
.width('100%')
.height(100)
实现一个数字轮播的效果:
参考代码
@Entry
@Component
struct Page01_Swiper {
// Swiper 基本使用
build() {
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
}
.width('100%')
.height(100)
}
}
1.3. 常用属性方法
autoPlay | boolean | 子组件是否自动播放。 默认值:false 说明: loop为false时,自动轮播到最后一页时停止轮播。手势切换后不是最后一页时继续播放。 |
interval | number | 使用自动播放时播放的时间间隔,单位为毫秒。 默认值:3000 |
vertical | boolean | 是否为纵向滑动。 默认值:false |
loop👎 | boolean | 是否开启无限循环播放。 设置为true时表示无限循环播放,设置为false时表示只播放一次。 默认值:true |
参考文档创建轮播 (Swiper)-构建布局-开发布局-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者 (huawei.com) |
使用上述属性,将轮播图调整为:
- 自动播放
- 播放间隔:4 秒钟
- 纵向滑动
基础模版
@Entry
@Component
struct Page02_SwiperAttribute {
build() {
Column() {
Text('Swiper常用属性')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
}
.width('100%')
.height(100)
}
.width('100%')
.height('100%')
}
}
参考代码:
@Entry
@Component
struct Page02_SwiperAttribute {
build() {
Column() {
Text('Swiper常用属性')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
}
.width('100%')
.height(160)
.loop(false) // 是否开启循环 true/false
.autoPlay(true) // 是否自动播放 true/false
.interval(4000) // 自动播放时间间隔 单位毫秒
.vertical(true) // 是否纵向滑动
}
.width('100%')
.height('100%')
}
}
1.4. 调整导航点
如果默认的导航点不满足效果,可以自行调整
导航点的调整可以分为 2 类:
- 显示 or 隐藏
- 导航点类型:
- 圆点(掌握)
- 数字(了解)
indicator | DotIndicator | DigitIndicator | boolean | 设置可选导航点指示器样式。 - DotIndicator:圆点指示器样式。 - DigitIndicator:数字指示器样式。 - boolean:是否启用导航点指示器。 默认值:true 默认类型:DotIndicator |
Swiper(){
// 略
}
// .indicator(false) // 关闭导航
// .indicator(Indicator.dot()) // 圆点指示器(默认)
// .indicator(Indicator.digit()) // 数字指示器
日常开发中 较为常见的是圆点指示器,咱们重点掌握如何调整他即可
Swiper(){
// 略
}
.indicator(
Indicator.dot()
// .xxx(设置圆点指示器的属性)
) // 圆点指示器(默认)
位置属性:
参数名 | 参数类型 | 必填项 | 参数描述 |
left | Length | 否 | 设置导航点距离Swiper组件左边的距离。 默认值:0 单位:vp |
top | Length | 否 | 设置导航点距离Swiper组件顶部的距离。 默认值:0 单位:vp |
right | Length | 否 | 设置导航点距离Swiper组件右边的距离。 默认值:0 单位:vp |
bottom | Length | 否 | 设置导航点距离Swiper组件底部的距离。 默认值:0 单位:vp |
样式属性:
参数名 | 参数类型 | 必填项 | 参数描述 |
itemWidth | Length | 否 | 设置Swiper组件圆点导航指示器的宽,不支持设置百分比。 默认值:6 单位:vp |
itemHeight | Length | 否 | 设置Swiper组件圆点导航指示器的高,不支持设置百分比。 默认值:6 单位:vp |
selectedItemWidth | Length | 否 | 设置选中Swiper组件圆点导航指示器的宽,不支持设置百分比。 默认值:12 单位:vp |
selectedItemHeight | Length | 否 | 设置选中Swiper组件圆点导航指示器的高,不支持设置百分比。 默认值:6 单位:vp |
color | ResourceColor | 否 | 设置Swiper组件圆点导航指示器的颜色。 默认值:'#182431'(10%透明度) |
selectedColor | ResourceColor | 否 | 设置选中Swiper组件圆点导航指示器的颜色。 默认值:'#007DFF' |
基础模版
@Entry
@Component
struct Page03_SwiperIndicator {
build() {
Column() {
Text('Swiper导航点')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
}
.width('100%')
.height(160)
}
.width('100%')
.height('100%')
}
}
参考代码:
@Entry
@Component
struct Page03_SwiperIndicator {
build() {
Column() {
Text('Swiper导航点')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
}
.width('100%')
.height(160)
// .indicator(false) // 关闭导航点
// .indicator(Indicator.digit()) // 数字导航点
.indicator(
Indicator.dot()
.left(10)// 左侧距离
.top(10)// 顶部距离
.bottom(10)// 底部距离
.right(10)// 右侧距离(距离属性组合使用,无需全部设置)
.itemWidth(20)// 指示器宽度
.itemHeight(20)// 指示器高度
.selectedItemWidth(30)// 选中指示器宽度
.selectedItemHeight(30)// 选中指示器高度
.selectedColor(Color.Yellow)// 选中指示器颜色
.color(Color.Blue) // 默认指示器宽度
) // 圆形导航点
}
.width('100%')
.height('100%')
}
}
1.5. 案例-小米有品
关键信息:
- 宽高:100%、160
- 循环、自动轮播,间隔 2000
- 圆点指示器:
- 选中颜色:白色
- 选中宽高:30、4
- 默认宽高:10、4
基础模版:
@Entry
@Component
struct Page04_SwiperDemo_xiaomi {
build() {
Column() {
Text('Swiper案例-小米')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Image($r('app.media.ic_swiper_xmyp01'))
Image($r('app.media.ic_swiper_xmyp02'))
Image($r('app.media.ic_swiper_xmyp03'))
Image($r('app.media.ic_swiper_xmyp04'))
}
}
.width('100%')
.height('100%')
}
}
参考代码:
@Entry
@Component
struct Page04_SwiperDemo_xiaomi {
build() {
Column() {
Text('Swiper案例-小米')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Image($r('app.media.ic_swiper_xmyp01'))
Image($r('app.media.ic_swiper_xmyp02'))
Image($r('app.media.ic_swiper_xmyp03'))
Image($r('app.media.ic_swiper_xmyp04'))
}
.width('100%')
.height(160)
.indicator(
Indicator.dot()// 圆形导航点
.selectedColor(Color.White)// 选中颜色
.selectedItemWidth(30)// 选中宽度
.selectedItemHeight(4)// 选中高度
.itemWidth(10)// 默认宽度
.itemHeight(4) // 默认高度
)
}
.width('100%')
.height('100%')
}
}
2. 样式&结构重用
随着页面复杂程度提高,页面中会有很多的样式&结构代码,其中难免重复的部分,如果可以提取出来重复使用,就可以提升编码效率,减少重复代码,提升代码可读性。
咱们来来学习3 种样式&结构复用的语法:
- @Styles: 抽取公共样式、事件
- @Extends:扩展组件样式、事件
- @Builder:轻量级的元素复用机制(结构、样式、事件)- 重点掌握
2.1. @Styles
@Styles 可以抽取 通用的事件和属性,比如:
基础模版:
@Entry
@Component
struct Page05_Styles {
@State bgColor: Color = Color.Gray
build() {
Column({ space: 10 }) {
Text('切换颜色')
.width(100)
.height(100)
.border({ width: 1 })
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
Button('切换颜色')
.width(100)
.height(100)
.border({ width: 1 })
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
}
.width('100%')
.height('100%')
}
}
试一试:
- 使用@Styles 抽取公共部分代码
- 需求:
- 宽高抽取到全局
- 背景色和点击事件抽取到组件内(需要通过 this 访问状态)
// 全局定义
@Styles function functionName() {
.通用属性()
.通用事件(()=>{})
}
@Entry
@Component
struct FancyUse {
// 在组件内定义
@Styles fancy() {
.通用属性()
.通用事件(()=>{
this.xxx
})
}
build() {
Column(){
// 使用
组件().fancy()
组件().functionName()
}
}
}
tips:
- @Styles 不支持传递参数
- 可以继续添加样式覆盖 @Styles 中定义的内容
- 组件内部写法,可以通过 this 访问组件属性
参考代码:
- 因为需要使用组件内部的状态,所以使用组件内的定义方式
@Styles
function xxx() {
.width(100)
.height(100)
.border({ width: 1 })
}
@Entry
@Component
struct Page05_Styles {
@State bgColor: Color = Color.Gray
// 组件内定义方法, 省略function关键字, 组件定义的函数
@Styles
yyy() {
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Pink
})
}
build() {
Column({ space: 10 }) {
Text('切换颜色')
.xxx()
.yyy()
Button('切换颜色')
.xxx()
.yyy()
}
.width('100%')
.height('100%')
}
}
2.2. @Extend
通过 Extend 可以扩展组件的样式、事件, 实现复用效果
功能展示:
抽取之后可以通过 textExtend 设置公共的外观、事件,并且传递参数
未抽取写法
@Entry
@Component
struct Index {
build() {
Swiper() {
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 1'
})
})
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 2'
})
})
Text('3')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 3'
})
})
}
.width('100%')
.height(200)
}
}
抽取之后的写法
Swiper() {
Text('1')
.textExtend(Color.Red,'轮播图 1')
Text('2')
.textExtend(Color.Green,'轮播图 2')
Text('3')
.textExtend(Color.Blue,'轮播图 3')
}
语法:
不同于@Styles,@Extend只能定义在全局,并且可以传递参数
// 定义
@Extend(组件名)
function functionName(参数1....) {
.属性()
.事件(()=>{})
}
// 使用
组件(){}
.functionName(参数1...)
比如给 Text 扩展内容可以这么写:
// 定义在全局
@Extend(Text)
function functionName(size:number) {
.fontSize(size)
.onClick(()=>{})
}
// 使用
Text()
.functionName(10)
tip:
扩展的组件和使用的组件同名,比如 @Extend(Text) ,那么后续通过 Text 才可以点出该扩展
试一试:模版代码,使用@Extend 抽取,实现如下效果
Swiper() {
Text('1')
.textExtend(Color.Red,'轮播图 1')
Text('2')
.textExtend(Color.Green,'轮播图 2')
Text('3')
.textExtend(Color.Blue,'轮播图 3')
}
基础模版:
@Entry
@Component
struct Extends_demo {
@State message: string = '样式&结构重复使用';
build() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Swiper() {
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 1'
})
})
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 2'
})
})
Text('3')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 3'
})
})
}
.width('100%')
.height(160)
}
.width('100%')
.height('100%')
}
}
参考代码:
@Extend(Text)
function TextExtend(color: ResourceColor, message: string) {
.textAlign(TextAlign.Center)
.backgroundColor(color)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: message
})
})
}
@Entry
@Component
struct Page06_Extend {
build() {
Column() {
Text('@Extends')
.fontSize(20)
.fontWeight(900)
.padding(10)
Swiper() {
Text('0')
.TextExtend(Color.Red,'轮播图 1')
Text('1')
.TextExtend(Color.Green,'轮播图 2')
Text('2')
.TextExtend(Color.Blue,'轮播图 3')
}
.width('100%')
.height(160)
}
.width('100%')
.height('100%')
}
}
2.3. @Builder
如果连结构也需要抽取,就可以使用@Builder,他也可以叫做 自定义构建函数
未抽取
Row() {
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_01'))
.width('80%')
Text('阿里拍卖')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 阿里拍卖'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_02'))
.width('80%')
Text('菜鸟')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 菜鸟'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_03'))
.width('80%')
Text('芭芭农场')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 芭芭农场'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_04'))
.width('80%')
Text('阿里药房')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 阿里药房'
})
})
}
抽取之后
Row() {
navItem($r('app.media.ic_reuse_01'),'阿里拍卖')
navItem($r('app.media.ic_reuse_02'),'菜鸟')
this.navItem($r('app.media.ic_reuse_03'),'芭芭农场')
this.navItem($r('app.media.ic_reuse_04'),'阿里药房')
}
语法:
// 自定义 全局 构建函数
@Builder function MyGlobalBuilderFunction(param1,param2...) {
// 结构、属性、事件放这里
}
// 使用
MyGlobalBuilderFunction(param1,param2...)
// 自定义 组件内 构建函数
@Builder MyBuilderFunction( param1,param2...) {
// 结构、属性、事件放这里
}
// 通过 this 来使用
this.MyBuilderFunction(param1,param2...)
比如,要抽取一个 Text 组件并且带有属性和事件
@Builder
function GlobalTextItem(title: string) {
Text(title)
.fontSize(30)
.onClick(() => {
// 逻辑略
})
}
@Entry
@Component
struct Day01_04_Builder {
build() {
Column({ space: 20 }) {
// 使用全局 Builder
GlobalTextItem('你好')
// 使用本地 Builder
this.LocalTextItem('西兰花炒蛋')
}
.width('100%')
.height('100%')
}
@Builder
LocalTextItem(title: string) {
Text(title)
.fontSize(30)
.onClick(() => {
// 逻辑略
})
}
}
tips:
- 组件内 自定义构建函数 需要通过 this 访问,内部可以通过 this 使用组件属性
- 全局 自定义构建函数 没有上述特点
试一试:
- 使用刚刚学习的 @Builder 抽取 column 中的结构
- 实现点击 弹出对应提示信息
- 前 2 个使用 全局的 Builder 抽取,后两个使用组件内部的 Builder
基础模版
// 👎 Styles与Extend只能封装样式与事件
@Entry
@Component
struct Page07_Builder {
build() {
Column({ space: 20 }) {
Row() {
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_01')).width('80%')
Text('阿里拍卖')
}
.width('25%')
.onClick(() => {
AlertDialog.show({ message: '点了 阿里拍卖' })
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_02')).width('80%')
Text('菜鸟')
}
.width('25%')
.onClick(() => {
AlertDialog.show({ message: '点了 菜鸟' })
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_03')).width('80%')
Text('芭芭农场')
}
.width('25%')
.onClick(() => {
AlertDialog.show({ message: '点了 芭芭农场' })
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_04')).width('80%')
Text('阿里药房')
}
.width('25%')
.onClick(() => {
AlertDialog.show({ message: '点了 阿里药房' })
})
}
}
.width('100%')
.height('100%')
.padding(10)
}
}
参考代码:
// 全局的 Builder
@Builder
function globalItem(icon: ResourceStr, title: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%');
Text(title);
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 ' + title
});
})
}
@Entry
@Component
struct Page07_Builder {
build() {
Column({ space: 20 }) {
Row() {
// 全局
globalItem($r('app.media.ic_reuse_01'), '阿里拍卖')
globalItem($r('app.media.ic_reuse_02'), '菜鸟')
// 组件内部
this.localItem($r('app.media.ic_reuse_03'), '芭芭农场')
this.localItem($r('app.media.ic_reuse_04'), '阿里药房')
}
}
.width('100%')
.height('100%')
}
@Builder
localItem(icon: ResourceStr, title: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%');
Text(title);
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 ' + title
});
})
}
}
2.4. @Extend、@Styles、@Builder 对比
名称 | 适合 | 参数 |
@Styles | 抽取 通用样式、事件 | 不可以传递参数 |
@Extend | 抽取 特定组件 样式、事件 | 可以传递参数 |
@Builder(重点掌握) | 抽取 结构、样式、事件 | 可以传递参数 |
@Styles 和@Extend 了解即可,@Builder 一定需要掌握:
因为后期有不少组件的参数必须结合@Builder 使用
3. 网格布局 Grid/GridItem
如果布局是由 很多的 行 和 列 所组成、行列可能需要合并、甚至滚动,就可以使用网格布局来实现
因为网格布局可以实现很多种不同的效果,咱们逐步进行讲解
- 固定行列数量(不滚动)
- 合并行列(不滚动)
- 设置滚动
- 代码控制滚动
- 自定义滚动条
3.1. 固定行列
首先来看看如何实现固定行列、不滚动
组件结构:
Grid() {
GridItem(){
// 展示的内容放在这里
}
GridItem(){
// 展示的内容放在这里
}
}
说明
- 子组件必须是GridItem组件,需要展示的内容设置在 GridItem 内部既可
- GridItem 只能有一个子组件
基本属性:
名称 | 参数类型 | 描述 |
columnsTemplate | string | 设置当前网格布局列的数量或最小列宽值,不设置时默认1列。 例如, '1fr 1fr 2fr' 是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。 |
rowsTemplate | string | 设置当前网格布局行的数量或最小行高值,不设置时默认1行。 例如, '1fr 1fr 2fr'是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。 |
columnsGap | Length | 设置列与列的间距。 默认值:0 |
rowsGap | Length | 设置行与行的间距。 默认值:0 |
试一试:
- 容器宽高:100%、300
- 行列占比如下:
参考代码:
@Entry
@Component
struct Page08_Grid {
build() {
Column() {
Text('@Builder')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
GridItem() {
Text('1')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
GridItem() {
Text('2')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
GridItem() {
Text('3')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
GridItem() {
Text('4')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
GridItem() {
Text('5')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
GridItem() {
Text('6')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor(Color.Blue)
}
.border({ width: 1 })
.columnsTemplate('1fr 2fr 1fr')
.rowsTemplate('1fr 1fr')
.width('100%')
.height(360)
.columnsGap(10)
.rowsGap(10)
}
.width('100%')
.height('100%')
}
}
3.2. 案例-淘宝二楼
基础模版
interface TaoBaoItemContent {
title: string
icon: ResourceStr // $r('图片名')返回的是 Resource 类型, ResourceStr 是联合类型 Resource|string
}
@Entry
@Component
struct Page09_GridDemo_TaoBao {
contentList: TaoBaoItemContent[] = [
{ title: '淘金币', icon: $r('app.media.ic_taobao_01') },
{ title: '一起摇现金', icon: $r('app.media.ic_taobao_02') },
{ title: '闲鱼', icon: $r('app.media.ic_taobao_03') },
{ title: '中通快递', icon: $r('app.media.ic_taobao_04') },
{ title: '芭芭农场', icon: $r('app.media.ic_taobao_05') },
{ title: '淘宝珍库', icon: $r('app.media.ic_taobao_06') },
{ title: '阿里拍卖', icon: $r('app.media.ic_taobao_07') },
{ title: '阿里药房', icon: $r('app.media.ic_taobao_08') },
{ title: '小黑盒', icon: $r('app.media.ic_taobao_09') },
{ title: '菜鸟', icon: $r('app.media.ic_taobao_10') },
{ title: 'U先试用', icon: $r('app.media.ic_taobao_11') },
{ title: '有好价', icon: $r('app.media.ic_taobao_12') },
{ title: '极有家', icon: $r('app.media.ic_taobao_13') },
{ title: '天猫榜单', icon: $r('app.media.ic_taobao_14') },
{ title: '天天特卖', icon: $r('app.media.ic_taobao_15') },
{ title: '每日好店', icon: $r('app.media.ic_taobao_16') },
{ title: '全球购', icon: $r('app.media.ic_taobao_17') },
{ title: '我的爱车', icon: $r('app.media.ic_taobao_18') },
{ title: '造点新货', icon: $r('app.media.ic_taobao_19') },
{ title: '首单优惠', icon: $r('app.media.ic_taobao_20') },
{ title: '潮Woo', icon: $r('app.media.ic_taobao_21') },
{ title: '亲宝贝', icon: $r('app.media.ic_taobao_22') },
{ title: '领券中心', icon: $r('app.media.ic_taobao_23') },
{ title: '天猫奢品', icon: $r('app.media.ic_taobao_24') },
{ title: 'iFashion', icon: $r('app.media.ic_taobao_25') }
]
build() {
Column() {
Column() {
// 顶部返回区域
Row() {
Image($r('app.media.ic_taobao_back'))
.fillColor(Color.White)
.width(30)
Text('最近使用')
.fontSize(20)
.fontColor(Color.White)
}
.width('100%')
.padding({ top: 40 })
// 搜索框区域
Stack() {
Text()
.width('100%')
.height(40)
.backgroundColor(Color.White)
.opacity(.3)
.borderRadius(20)
Row({ space: 10 }) {
Image($r('app.media.ic_taobao_search'))
.width(25)
.fillColor(Color.White)
Text('搜索')
.fontSize(15)
.fontColor(Color.White)
}
.padding({ left: 10 })
.width('100%')
}
.padding(10)
// Grid区域
}
}
.width('100%')
.height('100%')
.linearGradient({
colors: [
['#271b41', 0],
['#481736', 1],
]
})
}
}
需求:
- 实现布局效果
- 红框区域 抽取为 Builder
关键信息
- 红色区域宽高:100%、360
- 图片宽高:40*40
- 文字:大小 14 和图片间隔 10
- 列间隙:10
- 行列数:5*5
参考代码:
interface TaoBaoItemContent {
title: string
icon: ResourceStr // $r('图片名')返回的是 Resource 类型, ResourceStr 是联合类型 Resource|string
}
@Entry
@Component
struct Page09_GridDemo_TaoBao {
contentList: TaoBaoItemContent[] = [
{ title: '淘金币', icon: $r('app.media.ic_taobao_01') },
{ title: '一起摇现金', icon: $r('app.media.ic_taobao_02') },
{ title: '闲鱼', icon: $r('app.media.ic_taobao_03') },
{ title: '中通快递', icon: $r('app.media.ic_taobao_04') },
{ title: '芭芭农场', icon: $r('app.media.ic_taobao_05') },
{ title: '淘宝珍库', icon: $r('app.media.ic_taobao_06') },
{ title: '阿里拍卖', icon: $r('app.media.ic_taobao_07') },
{ title: '阿里药房', icon: $r('app.media.ic_taobao_08') },
{ title: '小黑盒', icon: $r('app.media.ic_taobao_09') },
{ title: '菜鸟', icon: $r('app.media.ic_taobao_10') },
{ title: 'U先试用', icon: $r('app.media.ic_taobao_11') },
{ title: '有好价', icon: $r('app.media.ic_taobao_12') },
{ title: '极有家', icon: $r('app.media.ic_taobao_13') },
{ title: '天猫榜单', icon: $r('app.media.ic_taobao_14') },
{ title: '天天特卖', icon: $r('app.media.ic_taobao_15') },
{ title: '每日好店', icon: $r('app.media.ic_taobao_16') },
{ title: '全球购', icon: $r('app.media.ic_taobao_17') },
{ title: '我的爱车', icon: $r('app.media.ic_taobao_18') },
{ title: '造点新货', icon: $r('app.media.ic_taobao_19') },
{ title: '首单优惠', icon: $r('app.media.ic_taobao_20') },
{ title: '潮Woo', icon: $r('app.media.ic_taobao_21') },
{ title: '亲宝贝', icon: $r('app.media.ic_taobao_22') },
{ title: '领券中心', icon: $r('app.media.ic_taobao_23') },
{ title: '天猫奢品', icon: $r('app.media.ic_taobao_24') },
{ title: 'iFashion', icon: $r('app.media.ic_taobao_25') }
]
build() {
Column() {
Column() {
// 顶部返回区域
this.backBuilder()
// 搜索框区域
this.searchBuilder()
// Grid区域
this.gridBuilder()
}
}
.width('100%')
.height('100%')
.linearGradient({
colors: [
['#271b41', 0],
['#481736', 1],
]
})
}
@Builder
backBuilder() {
// 顶部返回区域
Row() {
Image($r('app.media.ic_taobao_back'))
.fillColor(Color.White)
.width(30)
Text('最近使用')
.fontSize(20)
.fontColor(Color.White)
}
.width('100%')
.padding({ top: 40 })
}
@Builder
searchBuilder() {
// 搜索框区域
Stack() {
Text()
.width('100%')
.height(40)
.backgroundColor(Color.White)
.opacity(.3)
.borderRadius(20)
Row({ space: 10 }) {
Image($r('app.media.ic_taobao_search'))
.width(25)
.fillColor(Color.White)
Text('搜索')
.fontSize(15)
.fontColor(Color.White)
}
.padding({ left: 10 })
.width('100%')
}
.padding(10)
}
@Builder
gridBuilder() {
// Grid区域
Grid() {
ForEach(this.contentList, (item: TaoBaoItemContent, index: number) => {
GridItem() {
Column({ space: 10 }) {
Image(item.icon)
.width(40)
Text(item.title)
.fontColor(Color.White)
.fontSize(14)
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.width('100%')
.height(360)
}
}
3.3. 合并行列
接下来看看如何合并行列,不滚动
日常开发中除了大小相同的等比例网格布局,由不同大小的网格组成不均匀分布的网格布局场景在实际应用中也十分常见,如下图所示。
如果要实现这个效果 通过 GridItem 的如下属性即可:
名称 | 参数类型 | 描述 |
rowStart | number | 指定当前元素起始行号。 |
rowEnd | number | 指定当前元素终点行号。 |
columnStart | number | 指定当前元素起始列号。 |
columnEnd | number | 指定当前元素终点列号。 |
试一试:
改写为 |
小技巧:如何快速生成特定长度的数组
list: string[] = Array.from({ length: 30 })
希望数组多长,length 设置为多少即可
基本结构:
@Entry
@Component
struct Page10_Grid_Merge {
// 快速生成 12 个元素的数组
// Array.from 是 Array 这个类上面的静态方法
// {length:12} 是一个对象,有 length 属性,值为 12
nums: number[] = Array.from({ length: 12 })
build() {
Column() {
Text('合并行列')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.nums, (item: number, index: number) => {
GridItem() {
Text(index + '')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor('#9dc3e6')
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height(260)
.rowsGap(10)
.columnsGap(10)
.padding(10)
}
.width('100%')
.height('100%')
}
}
核心步骤:
- 根据索引找到关键的 GridItem
- 设置跨行跨列
参考代码:
@Entry
@Component
struct Page10_Grid_Merge {
// 快速生成 12 个元素的数组
// Array.from 是 Array 这个类上面的静态方法
// {length:12} 是一个对象,有 length 属性,值为 12
nums: number[] = Array.from({ length: 12 })
build() {
Column() {
Text('合并行列')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.nums, (item: number, index: number) => {
if (index === 2) {
GridItem() {
Text(index + '')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor('#9dc3e6')
.columnStart(3)
.columnEnd(4)
} else if (index === 3) {
GridItem() {
Text(index + '')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor('#9dc3e6')
.rowStart(2)
.rowEnd(3)
} else {
GridItem() {
Text(index + '')
.fontColor(Color.White)
.fontSize(30)
}
.backgroundColor('#9dc3e6')
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width('100%')
.height(260)
.rowsGap(10)
.columnsGap(10)
.padding(10)
}
.width('100%')
.height('100%')
}
}
知道如何实现跨行之后,类似的场景都可以考虑通过 Grid/GridItem来实现,比如:
3.4. 设置滚动
日常开发中,可以滚动的网格布局也经常出现,比如文件管理、购物、视频列表等,咱们来看看如何实现
设置方式:
- 水平滚动:设置的是rowsTemplate,Grid的滚动方向为水平方向。
- 垂直滚动:设置的是columnsTemplate,Grid的滚动方向为垂直方向
试一试:
咱们来分别实现,横向和竖向滚动
基础模版:
// 为 Text 扩展属性 newExtend
// newExtend 设置了一些Text 组件的属性
@Extend(Text)
function newExtend() {
.height('100%')
.fontSize(30)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
@Entry
@Component
struct Page11_Grid_Scroll {
// 长度为 10 每一项都为 undefined 的数组
list: string[] = Array.from({ length: 30 })
build() {
Grid() {
ForEach(this.list, (item: string, index) => {
GridItem() {
Text((index + 1).toString())
.newExtend()
}
.backgroundColor('#0094ff')
.border({ width: 1 })
})
}
.width('100%')
.height(300)
}
}
参考代码:
// 为 Text 扩展属性 newExtend
// newExtend 设置了一些Text 组件的属性
@Extend(Text)
function newExtend() {
.width('100%')
.height('100%')
.fontSize(30)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
@Entry
@Component
struct Page11_Grid_Scroll {
// 长度为 10 每一项都为 undefined 的数组
list: string[] = Array.from({ length: 30 })
build() {
Column() {
Text('设置滚动')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.list, (item: string, index) => {
GridItem() {
Text((index + 1).toString())
.newExtend()
}
.padding(5)
.backgroundColor('#0094ff')
.height('25%') // 竖向滚动-通过 height 设置高度
.width('25%') // 横向滚动 通过 width 设置宽度
})
}
.columnsTemplate('1fr 1fr 1fr') // 竖向滚动 固定列数
// .rowsTemplate('1fr 1fr 1fr') // 横向滚动 固定行数
.rowsGap(10)
.columnsGap(10)
.width('100%')
.height(300)
.border({ width: 1 })
.padding(5)
}
.width('100%')
.height('100%')
}
}
3.5. 案例-小米有品-滚动导航
需求:
- 渲染列表
- 横向滚动
- 滚动条的效果一会再实现
关键尺寸:
- 宽高: 100%、160
- 图片宽:容器宽度 80%
- 文字大小:12
基础结构:
interface NavItem {
title: string
icon: ResourceStr // 联合属性 Resource | string
}
@Entry
@Component
struct Page12_GridDemo_Xiaomi {
// 数据 只需要渲染,所以没有使用@State 修饰
navList: NavItem[] = [
{ title: '上新精选', icon: $r('app.media.ic_xiaomi_nav_01') },
{ title: '智能家电', icon: $r('app.media.ic_xiaomi_nav_02') },
{ title: '小米众筹', icon: $r('app.media.ic_xiaomi_nav_03') },
{ title: '有品会员', icon: $r('app.media.ic_xiaomi_nav_04') },
{ title: '有品秒杀', icon: $r('app.media.ic_xiaomi_nav_05') },
{ title: '原产地', icon: $r('app.media.ic_xiaomi_nav_06') },
{ title: '生活优选', icon: $r('app.media.ic_xiaomi_nav_07') },
{ title: '手机', icon: $r('app.media.ic_xiaomi_nav_08') },
{ title: '小米自营', icon: $r('app.media.ic_xiaomi_nav_09') },
{ title: '茅台酒饮', icon: $r('app.media.ic_xiaomi_nav_10') },
{ title: '鞋服饰品', icon: $r('app.media.ic_xiaomi_nav_11') },
{ title: '家纺餐厨', icon: $r('app.media.ic_xiaomi_nav_12') },
{ title: '食品生鲜', icon: $r('app.media.ic_xiaomi_nav_13') },
{ title: '好惠买', icon: $r('app.media.ic_xiaomi_nav_14') },
{ title: '家具家装', icon: $r('app.media.ic_xiaomi_nav_15') },
{ title: '健康养生', icon: $r('app.media.ic_xiaomi_nav_16') },
{ title: '有品海购', icon: $r('app.media.ic_xiaomi_nav_17') },
{ title: '个护清洁', icon: $r('app.media.ic_xiaomi_nav_18') },
{ title: '户外运动', icon: $r('app.media.ic_xiaomi_nav_19') },
{ title: '3C数码', icon: $r('app.media.ic_xiaomi_nav_20') }
]
build() {
Column() {
Text('设置滚动')
.fontSize(20)
.fontWeight(900)
.padding(10)
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#f5f5f5')
}
}
参考答案:
interface NavItem {
title: string
icon: ResourceStr // 联合属性 Resource | string
}
@Entry
@Component
struct Page12_GridDemo_Xiaomi {
// 数据 只需要渲染,所以没有使用@State 修饰
navList: NavItem[] = [
{ title: '上新精选', icon: $r('app.media.ic_xiaomi_nav_01') },
{ title: '智能家电', icon: $r('app.media.ic_xiaomi_nav_02') },
{ title: '小米众筹', icon: $r('app.media.ic_xiaomi_nav_03') },
{ title: '有品会员', icon: $r('app.media.ic_xiaomi_nav_04') },
{ title: '有品秒杀', icon: $r('app.media.ic_xiaomi_nav_05') },
{ title: '原产地', icon: $r('app.media.ic_xiaomi_nav_06') },
{ title: '生活优选', icon: $r('app.media.ic_xiaomi_nav_07') },
{ title: '手机', icon: $r('app.media.ic_xiaomi_nav_08') },
{ title: '小米自营', icon: $r('app.media.ic_xiaomi_nav_09') },
{ title: '茅台酒饮', icon: $r('app.media.ic_xiaomi_nav_10') },
{ title: '鞋服饰品', icon: $r('app.media.ic_xiaomi_nav_11') },
{ title: '家纺餐厨', icon: $r('app.media.ic_xiaomi_nav_12') },
{ title: '食品生鲜', icon: $r('app.media.ic_xiaomi_nav_13') },
{ title: '好惠买', icon: $r('app.media.ic_xiaomi_nav_14') },
{ title: '家具家装', icon: $r('app.media.ic_xiaomi_nav_15') },
{ title: '健康养生', icon: $r('app.media.ic_xiaomi_nav_16') },
{ title: '有品海购', icon: $r('app.media.ic_xiaomi_nav_17') },
{ title: '个护清洁', icon: $r('app.media.ic_xiaomi_nav_18') },
{ title: '户外运动', icon: $r('app.media.ic_xiaomi_nav_19') },
{ title: '3C数码', icon: $r('app.media.ic_xiaomi_nav_20') }
]
build() {
Column() {
Text('小米有品')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.navList, (item: NavItem) => {
GridItem() {
Column() {
Image(item.icon)
.width('80%')
Text(item.title)
.fontSize(12)
}
.height('100%')
}
.width('20%')
})
}
.rowsTemplate('1fr 1fr')
.height(160)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(5)
.padding({ bottom: 10 })
.scrollBar(BarState.Off) // 关闭滚动条
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#f5f5f5')
}
}
3.6. 控制器对象-控制滚动
日常开发中如果有类似需求就需要用到控制器对象啦:
- 代码控制滚动
- 自定义滚动条
咱们先来看看如何通过控制器对象来控制 Grid 滚动
基础模版:
@Entry
@Component
struct Page13_Grid_Scroller {
nums: number[] = Array.from({ length: 200 })
build() {
Column() {
Text('控制器-代码控制滚动')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.nums, (item: number, index: number) => {
GridItem() {
Text(index + 1 + '')
}
.backgroundColor('#0094ff')
.width('25%')
})
}
.padding(10)
.height(450)
.rowsGap(10)
.columnsGap(10)
.rowsTemplate('1fr 1fr 1fr 1fr')
Row() {
Button('上一页')
.width(100)
.onClick(() => {
})
Button('下一页')
.width(100)
.onClick(() => {
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
}
}
核心步骤:
- 创建 Scroller 对象(控制器对象)
- 设置给 Grid
- 调用 Scroller 对象的 scrollPage 方法
// 1.创建 Scroller 对象(new 关键字,调用Scroller函数,返回一个Scroller的对象)
scroller: Scroller = new Scroller()
// 2.设置给 Grid:这个属性可选,所以之前不设置也不会报错
Grid(this.scroller) {
// ...
}
// 3.调用 Scroller 对象的 scrollPage 方法即可实现滚动
this.scroller.scrollPage({
next:true // 下一页
next:false // 上一页
})
参考代码:
@Entry
@Component
struct Page13_Grid_Scroller {
nums: number[] = Array.from({ length: 200 })
// 控制器对象,不是状态属性,不需要添加任何修饰符
scroller: Scroller = new Scroller()
build() {
Column() {
Text('控制器-代码控制滚动')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid(this.scroller) {
ForEach(this.nums, (item: number, index: number) => {
GridItem() {
Text(index + 1 + '')
}
.backgroundColor('#0094ff')
.width('25%')
})
}
.padding(10)
.height(450)
.rowsGap(10)
.columnsGap(10)
.rowsTemplate('1fr 1fr 1fr 1fr')
Row() {
Button('上一页')
.width(100)
.onClick(() => {
// 上一页
this.scroller.scrollPage({ next: false })
})
Button('下一页')
.width(100)
.onClick(() => {
// 下一页
this.scroller.scrollPage({ next: true })
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
}
}
3.7. 控制器对象-自定义滚动条
基础模版
@Entry
@Component
struct Page14_Grid_ScrollBar {
// 长度为 30 每一项都为 undefined 的数组
list: string[] = Array.from({ length: 30 })
build() {
Column() {
Text('控制器-自定义滚动条')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.list, (item: string, index) => {
GridItem() {
this.ItemBuilder(index)
}
.padding(5)
.height('30%')
.width('25%')
})
}
.rowsTemplate('1fr 1fr 1fr') // 竖向滚动
.rowsGap(10)
.width('100%')
.height(300)
.border({ width: 1 })
.padding(5)
.scrollBar(BarState.Off) // 关闭
}
.width('100%')
.height('100%')
}
@Builder
ItemBuilder(index: number) {
Text((index + 1).toString())
.backgroundColor('#0094ff')
.width('100%')
.height('100%')
.fontSize(30)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
}
核心步骤:
- 隐藏默认滚动条
- 使用ScrollBar组件自定义滚动条
- ScrollBar 和 Grid 设置同一个 Scroller(控制器对象)
- 通过 参数 和 属性控制 ScrollBa
第一步:首先通过 Grid 的 scrollBar 属性关闭滚动条
属性名 | 类型 | 说明 |
scrollBar | BarState | 设置滚动条状态。 默认值:BarState.auto BarState.off 关闭 BarState.on 常驻 BarState.auto 按需显示 |
第二步:使用ScrollBar组件自定义滚动条
参数名 | 参数类型 | 必填 | 参数描述 |
scroller | Scroller | 是 | 可滚动组件的控制器。用于与可滚动组件进行绑定。 |
direction | ScrollBarDirection | 否 | 滚动条的方向,控制可滚动组件对应方向的滚动。 默认值:ScrollBarDirection.Vertical |
state | BarState | 否 | 滚动条状态。 |
// 创建控制器对象
scroller: Scroller = new Scroller()
// 设置给 Grid 组件
Grid(this.scroller){
// 略
}
// 设置给 ScrollBar 组件
// 和 Grid 设置的是同一个
ScrollBar({
scroller: this.scroller,
direction: ScrollBarDirection.Horizontal // 方向
}) {
// 滚动内容 设置外观即可
Text()
}
// 设置外观
试一试:
- 禁用默认的 scrollBar
- 使用ScrollBar组件进行自定义滚动条
参考代码:
@Entry
@Component
struct Page14_Grid_ScrollBar {
// 长度为 30 每一项都为 undefined 的数组
list: string[] = Array.from({ length: 30 })
// 创建控制器对象
scroller: Scroller = new Scroller()
build() {
Column() {
Text('控制器-自定义滚动条')
.fontSize(20)
.fontWeight(900)
.padding(10)
// 设置控制器对象给 Grid
Grid(this.scroller) {
ForEach(this.list, (item: string, index) => {
GridItem() {
this.ItemBuilder(index)
}
.padding(5)
.height('30%')
.width('25%')
})
}
.rowsTemplate('1fr 1fr 1fr') // 竖向滚动
.rowsGap(10)
.width('100%')
.height(300)
.border({ width: 1 })
.padding(5)
.scrollBar(BarState.Off) // 关闭
// 自定义滚动条
ScrollBar({
scroller: this.scroller,// 和 Grid 同一个控制器对象
direction: ScrollBarDirection.Horizontal,
}) {
Text()
.width(40)
.height(20)
.backgroundColor(Color.Orange)
}
.width(200)
.height(20)
.backgroundColor(Color.Red)
}
.width('100%')
.height('100%')
}
@Builder
ItemBuilder(index: number) {
Text((index + 1).toString())
.backgroundColor('#0094ff')
.width('100%')
.height('100%')
.fontSize(30)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}
}
3.8. 案例-小米有品-滚动导航-自定义滚动条
基础模版
interface XMNavItem {
title: string
icon: ResourceStr // 联合属性 Resource | string
}
@Entry
@Component
struct Page15_GridDemo_Xiaomi_Scrollbar {
// 数据 只需要渲染,所以没有使用@State 修饰
navList: XMNavItem[] = [
{ title: '上新精选', icon: $r('app.media.ic_xiaomi_nav_01') },
{ title: '智能家电', icon: $r('app.media.ic_xiaomi_nav_02') },
{ title: '小米众筹', icon: $r('app.media.ic_xiaomi_nav_03') },
{ title: '有品会员', icon: $r('app.media.ic_xiaomi_nav_04') },
{ title: '有品秒杀', icon: $r('app.media.ic_xiaomi_nav_05') },
{ title: '原产地', icon: $r('app.media.ic_xiaomi_nav_06') },
{ title: '生活优选', icon: $r('app.media.ic_xiaomi_nav_07') },
{ title: '手机', icon: $r('app.media.ic_xiaomi_nav_08') },
{ title: '小米自营', icon: $r('app.media.ic_xiaomi_nav_09') },
{ title: '茅台酒饮', icon: $r('app.media.ic_xiaomi_nav_10') },
{ title: '鞋服饰品', icon: $r('app.media.ic_xiaomi_nav_11') },
{ title: '家纺餐厨', icon: $r('app.media.ic_xiaomi_nav_12') },
{ title: '食品生鲜', icon: $r('app.media.ic_xiaomi_nav_13') },
{ title: '好惠买', icon: $r('app.media.ic_xiaomi_nav_14') },
{ title: '家具家装', icon: $r('app.media.ic_xiaomi_nav_15') },
{ title: '健康养生', icon: $r('app.media.ic_xiaomi_nav_16') },
{ title: '有品海购', icon: $r('app.media.ic_xiaomi_nav_17') },
{ title: '个护清洁', icon: $r('app.media.ic_xiaomi_nav_18') },
{ title: '户外运动', icon: $r('app.media.ic_xiaomi_nav_19') },
{ title: '3C数码', icon: $r('app.media.ic_xiaomi_nav_20') }
]
build() {
Column() {
Text('小米有品')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid() {
ForEach(this.navList, (item: XMNavItem) => {
GridItem() {
Column() {
Image(item.icon)
.width('80%')
Text(item.title)
.fontSize(12)
}
.height('100%')
}
.width('20%')
})
}
.rowsTemplate('1fr 1fr')
.height(160)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(5)
.padding({ bottom: 10 })
.scrollBar(BarState.Off) // 关闭滚动条
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#f5f5f5')
}
}
核心步骤:
- 创建控制器对象(不需要 @State 装饰)
- Grid:设置控制器对象
- ScrollBar
- 添加ScrollBar组件(容器),添加子组件(导航点)
- 设置 参数,scroller,direction
- 设置 属性,调整外观
参考代码:
interface XMNavItem {
title: string
icon: ResourceStr // 联合属性 Resource | string
}
@Entry
@Component
struct Page15_GridDemo_Xiaomi_Scrollbar {
// 数据 只需要渲染,所以没有使用@State 修饰
navList: XMNavItem[] = [
{ title: '上新精选', icon: $r('app.media.ic_xiaomi_nav_01') },
{ title: '智能家电', icon: $r('app.media.ic_xiaomi_nav_02') },
{ title: '小米众筹', icon: $r('app.media.ic_xiaomi_nav_03') },
{ title: '有品会员', icon: $r('app.media.ic_xiaomi_nav_04') },
{ title: '有品秒杀', icon: $r('app.media.ic_xiaomi_nav_05') },
{ title: '原产地', icon: $r('app.media.ic_xiaomi_nav_06') },
{ title: '生活优选', icon: $r('app.media.ic_xiaomi_nav_07') },
{ title: '手机', icon: $r('app.media.ic_xiaomi_nav_08') },
{ title: '小米自营', icon: $r('app.media.ic_xiaomi_nav_09') },
{ title: '茅台酒饮', icon: $r('app.media.ic_xiaomi_nav_10') },
{ title: '鞋服饰品', icon: $r('app.media.ic_xiaomi_nav_11') },
{ title: '家纺餐厨', icon: $r('app.media.ic_xiaomi_nav_12') },
{ title: '食品生鲜', icon: $r('app.media.ic_xiaomi_nav_13') },
{ title: '好惠买', icon: $r('app.media.ic_xiaomi_nav_14') },
{ title: '家具家装', icon: $r('app.media.ic_xiaomi_nav_15') },
{ title: '健康养生', icon: $r('app.media.ic_xiaomi_nav_16') },
{ title: '有品海购', icon: $r('app.media.ic_xiaomi_nav_17') },
{ title: '个护清洁', icon: $r('app.media.ic_xiaomi_nav_18') },
{ title: '户外运动', icon: $r('app.media.ic_xiaomi_nav_19') },
{ title: '3C数码', icon: $r('app.media.ic_xiaomi_nav_20') }
]
// 创建控制器对象
scroller: Scroller = new Scroller()
build() {
Column() {
Text('小米有品')
.fontSize(20)
.fontWeight(900)
.padding(10)
Grid(this.scroller) {
ForEach(this.navList, (item: XMNavItem) => {
GridItem() {
Column() {
Image(item.icon)
.width('80%')
Text(item.title)
.fontSize(12)
}
.height('100%')
}
.width('20%')
})
}
.rowsTemplate('1fr 1fr')
.height(160)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(5)
.padding({ bottom: 10 })
.scrollBar(BarState.Off) // 关闭滚动条
// 自定义滚动条
ScrollBar({
scroller: this.scroller,
direction: ScrollBarDirection.Horizontal, // 横向滚动
state: BarState.On // 持续显示
}) {
Text()
.height(5)
.width(20)
.backgroundColor(Color.Orange)
.borderRadius(3)
}
.width(50)
.height(5)
.backgroundColor(Color.Gray)
.borderRadius(3)
.offset({ y: -10 })
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#f5f5f5')
}
}