1.Tabs(选项卡)
1.1 概述
Tabs组件的页面组成包含两个部分,分别是TabContent和TabBar。TabContent是内容页,TabBar是导航页签栏。
TabBar是导航页签栏,页面结构如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。
1.2 基本使用
Tabs使用花括号包裹TabContent,每一个TabContent对应的内容需要有一个页签,可以通过TabContent的tabBar属性进行配置。代码如下
bash
Tabs() {
TabContent() {
Text('首页的内容').fontSize(30)
}
.tabBar('首页')
TabContent() {
Text('推荐的内容').fontSize(30)
}
.tabBar('推荐')
TabContent() {
Text('发现的内容').fontSize(30)
}
.tabBar('发现')
TabContent() {
Text('我的内容').fontSize(30)
}
.tabBar("我的")
}

1.3 导航位置
barPosition 属性用来设置导航的位置,如下图所示
说明
● vertical为false时,tabbar的宽度默认为撑满屏幕的宽度,需要设置barWidth为合适值。
● vertical为true时,tabbar的高度默认为实际内容的高度,需要设置barHeight为合适值。
1.4 禁止滑动切换
控制滑动切换的属性为scrollable,默认值为true,表示可以滑动,若要限制滑动切换页签则需要设置为false。
bash
Tabs({barPosition: BarPosition.Start}){
...
}.scrollable(false)
1.5 滚动导航栏
当导航页签栏选项比较多时,可以让导航页签滚动
bash
Tabs({ barPosition: BarPosition.Start }) {
// TabContent的内容:关注、视频、游戏、数码、科技、体育、影视、人文、艺术、自然、军事
...
}
.barMode(BarMode.Scrollable)
1.6 自定义导航栏
对于底部导航栏,一般作为应用主页面功能区分,为了更好的用户体验,会以"文字+图标"表示页签内容,这种情况下,需要自定义导航页签的样式。
1.6.1自定义导航页签
自定义导航每一个页签包含四部分内容
- 页签文字
- 页签未选中图片
- 页签选中时图片
- 页签的索引
创建一个@Builder tabBuilder(){...}函数,用于构建自定义导航页签内容。
bash
/*
title: 页签标题
targetIndex: 页签的索引
selectedImg: 选中时图标
normalImg: 未选中时
*/
//当前选中页签索引
@State currentIndex: number = 0
@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
})
}
在调用TabContent对应.tabBar(this.tabBuilder(...)),传入相对应的参数。代码如下
bash
@Entry
@Component
struct TabsDemo {
build() {
Tabs({barPosition: BarPosition.End}){
TabContent(){
Text('首页')
}.tabBar(this.tabBuilder('首页',0,$r('app.media.home_selected'),$r('app.media.home_normal')))
TabContent(){
Text('推荐')
}.tabBar(this.tabBuilder('推荐',1,$r('app.media.home_selected'),$r('app.media.home_normal')))
TabContent(){
Text('热门')
}.tabBar(this.tabBuilder('热门',2,$r('app.media.home_selected'),$r('app.media.home_normal')))
TabContent(){
Text('文化')
}.tabBar(this.tabBuilder('文化',3,$r('app.media.home_selected'),$r('app.media.home_normal')))
}
})
}
如下图所示,此时点击页签,能够实现选中与未选中的变色效果。
但是此时还有一个问题,点击页签时并不能同步切换内容页。
1.6.2 点击页签同步切换内容页
想要实现点击页签同步切换页面,需要让Tabs与TabsController相关联,TabsController是用来控制页面切换的控制器,并调用TabsController的changeIndex(索引)切换内容页。
- 创建TabController与Tabs关联
bash
tabsController: TabsController = new TabsController()
build() {
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
...
}
}
- 给自定义页签设置点击事件,调用TabController的changeIndex(索引)切换页签。
1.6.3滑动内容页同步切换页签
在自定义导航栏的情况下,滑动内容页时,页签是不会同步切换的;此时需要监听内容页的改变,手动进行切换;
bash
Tabs({ barPosition: BarPosition.End, controller: this.tabController }) {
TabContent() {
Text('首页内容')
}.tabBar(this.tabBuilder('首页', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))
...
}.onChange((index) => {
this.currentIndex = index
})
效果如下
2.List(列表)
2.1 基本概念
List列表是一种复杂容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。使用列表可以轻松高效地显示结构化、可滚动的信息。

2.2 基本使用
List和ListItem是结合起来使用的,List表示列表容器,而ListItem表示列表中的列表项。
如下图所示,这是一个最简单的列表,每一个列表项显示一个文本

bash
@Entry
@Component
struct CityList {
build() {
List() {
ListItem() {
Text('北京').fontSize(24)
}
ListItem() {
Text('杭州').fontSize(24)
}
ListItem() {
Text('上海').fontSize(24)
}
}
.backgroundColor('#FFF1F3F5')
.alignListItem(ListItemAlign.Center)
}
}
2.3 循环渲染
如果List种ListItem列表项非常多,肯定不能一个一个列举,这个时候需要用到ForEach循环渲染。ForEach循环渲染的格式如下
bash
ForEach(数据集;(元素)=>{
//需要虚幻渲染的内容
})
比如我现在想要使用List列表显示中国的所有省会城市。
bash
@Entry
@Component
struct CityList {
// 定义一个字符串数组,包含中国所有的省会城市
citys: string[] = [
"北京",
"上海",
"天津",
"重庆",
"哈尔滨", // 黑龙江省
"长春", // 吉林省
"沈阳", // 辽宁省
"呼和浩特", // 内蒙古自治区
"石家庄", // 河北省
"太原", // 山西省
"西安", // 陕西省
"兰州", // 甘肃省
"西宁", // 青海省
"银川", // 宁夏回族自治区
"乌鲁木齐", // 新疆维吾尔自治区
"南宁", // 广西壮族自治区
"广州", // 广东省
"海口", // 海南省
"成都", // 四川省
"贵阳", // 贵州省
"昆明", // 云南省
"拉萨", // 西藏自治区
"郑州", // 河南省
"济南", // 山东省
"南京", // 江苏省
"杭州", // 浙江省
"合肥", // 安徽省
"福州", // 福建省
"台北", // 台湾省(注意:政治和实际情况可能有所不同)
"南昌", // 江西省
"长沙", // 湖南省
"武汉",// 湖北省
];
build() {
List() {
ForEach(this.citys, (item: string) => {
ListItem() {
Text(item).fontSize(24)
}
})
}
.backgroundColor('#FFF1F3F5')
.alignListItem(ListItemAlign.Center)
}
}
2.4 列表样式
- 设置列表项间距
bash
List({ space: 10 }) {
// ...
}
- 设置列表项分割线
bash
List() {
// ...
}
.divider({
strokeWidth: 0.5, //分割线的粗细
color:Color.Black //分割线的颜色
})
- 设置滚动条
bash
List() {
// ...
}
.scrollBar(BarState.Auto) //默认不显示,滚动时显示,2秒后消失
修改上面的案例,给列表加入自定义样式,代码如下
bash
List({ space: 20 }) {
ForEach(this.citys, (item: string) => {
ListItem() {
Text(item).fontSize(24)
}
})
}
.backgroundColor('#FFF1F3F5')
.alignListItem(ListItemAlign.Center) //交叉轴居中显示
.divider({ //加入分割线
strokeWidth:1, //线条粗细
startMargin:20, //线条开始外边距
endMargin:20, //线条结尾外边距
color:'#dedede'
})
.scrollBar(BarState.Auto) //默认不显示,滚动时显示,2秒后消失

2.5 复杂列表
案例需求:如下图所示是一个任务列表
- 每一个列表项包含:任务标题、任务创建时间、任务选中状态
- 点击添加时,添加列项到任务列表
- 点击删除时,将处于选中状态的列表项从列表中删除
- 点击取消时,列表项处于不可编辑状态(不显示复选框);长按列表项时,列表项处于可编辑状态(显示复选框)

2.5.1复杂列表的设计思路
当一个列表项显示的内容比较多时,一般会将列表项的UI和列表项的数据分离开来。如下图所示,是一个任务列表的列表项;

左图:用一个类来封装列表项的数据;
右图:将列表项的UI用自定义组件来表示;
合并:最后列表项的UI和数据绑定即可
多个列表项:采用ForEach循环渲染列表项就可以了

2.5.2 创建列表项数据模型
分析页面发现,每一个ListItem拥有三个数据,分别是:标题、创建时间、选中状态,根据编辑状态决定是否显示复选框。
bash
class TargetItemData {
title: string //目标标题
createTime: string //创建时间
checkStatus: boolean //选中状态 //1未选中,2选中
constructor(title: string //目标标题
, checkStatus: boolean //选中状态
) {
this.title = title;
this.createTime = this.getCurrentTime();
this.checkStatus = checkStatus;
}
//获取当前系统时间
getCurrentTime(): string {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let hours = date.getHours();
let minutes = date.getMinutes().toString();
if (Number.parseInt(minutes) < 10) {
minutes = `0${minutes}`;
}
let second = date.getSeconds().toString();
if (Number.parseInt(second) < 10) {
second = `0${second}`;
}
return `${year}/${month}/${day} ${hours}:${minutes}:${second}`;
}
}
2.5.3 创建列表项组件
分析页面,每一个Item组件包括:一个标题Text、一个时间Text、一个复选框CheckBox;其中CheckBox根据状态决定显示或者不显示。
bash
@Entry
@Component
struct Index {
//2.创建Item组件,并与Item数据模型绑定
@State isEditModel: number = 1 //是否编辑(1未编辑、2编辑)
@Builder
targetItem(data: TargetItemData) {
Row() {
Column() {
Text(data.title).width('100%')
.fontSize(18)
Text(`创建时间:${data.createTime}`).fontSize(12)
.fontColor('#A5A6AA')
.margin({ top: 8 })
}.alignItems(HorizontalAlign.Start)
.layoutWeight(9)
if (this.isEditModel == 2) {
Checkbox().select(data.checkStatus)
.layoutWeight(1)
.onChange((status) => {
data.checkStatus = status
})
}
}
.padding({
left: 12, right: 12
})
.backgroundColor(Color.White)
.height(68)
.border({ radius: 15 })
.margin({ left: 12, right: 12 })
}
build() {
//...
}
}
2.5.4 循环渲染列表项
分析主页面的布局结构,如下图所示,我们先把中间列表这部分写出来,其他的先放一下后面再完成
bash
//1.定义Item数据模型
//...
@Entry
@Component
struct Index {
//2.创建Item组件与Item数据模型绑定
//...
//3.创建Item数据集,并循环渲染到List容器中
@State targets: Array<TargetItemData> = [
new TargetItemData('运动', false),
new TargetItemData('读书', false),
new TargetItemData('听音乐', false),
new TargetItemData('看电影', false),
new TargetItemData('旅游', false)
]
build() {
Column() {
List({ space: 20 }) {
ForEach(this.targets, (item: TargetItemData) => {
ListItem() {
this.targetItem(item)
}
})
}.margin({ top: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#E9E9EB')
}
}
2.5.5 给列表项添加长按事件
给ListItem添加长按事件,检测到手指长按时改变Item状态。
bash
ListItem() {
this.targetItem(item)
}.gesture(LongPressGesture({ duration: 500 }).onAction((event) => {
this.isEditModel = 2 //编辑模式
}))
2.5.6 新增/删除列表项
当点击新增按钮时,新增一个列表项;点击删除按钮时,删除选中的列表项。
bash
@Entry
@Component
struct Index {
//...
@State isEditModel: number = 1 //是否编辑(1未编辑、2编辑)
build() {
Column() {
List({ space: 20 }) {
//...
}.margin({ top: 20 })
.layoutWeight(9) //权重9
//操作按钮
Column() {
if (this.isEditModel == 1) {
Button('新增').width('80%')
.onClick(() => {
this.targets.push(new TargetItemData('钓鱼',false))
})
} else {
Button('删除').width('80%')
.onClick(() => {
let leftData = this.targets.filter((item) => item.checkStatus == false) this.targets = leftData;
this.isEditModel = 1 //设置为未编辑状态
})
}
}.width('100%')
.layoutWeight(1) //权重1
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#E9E9EB')
}
}
3. Swiper(轮播)
3.1 基本概念
Swiper一般用来显示轮播图,在很多应用的首页都会有轮播图展示广告或者一些重要的信息。如下图所示,轮播展示几张图片。
3.2 基本使用
Swiper的基本使用也非常简单,只需要以下两个步骤即可
● 准备轮播数据(我这里就是4张图片,如果每一个轮播数据比较多也可以封装成对象)
● 循环渲染轮播数据
代码如下
bash
@Entry
@Component
struct Index {
//1. 准备轮播数据
swiperImages: Resource[] = [
$r('app.media.fig1'),
$r('app.media.fig2'),
$r('app.media.fig3'),
$r('app.media.fig4'),
]
build() {
Column() {
Swiper() {
//2.循环轮播数据
ForEach(this.swiperImages, (img: Resource) => {
Image(img).borderRadius(16)
.width('100%')
})
}
.margin({ top: 12 })
.autoPlay(true) //自动播放
}.margin({
left: 12, right: 12
})
}
}
3.3 导航点指示器
bash
Swiper(){
ForEach(this.imageArray,(item:Resource)=>{
Image(item).width('100%').height(200)
.borderRadius(10)
})
}
//.indicator(false) true表示有导航点、false表示没有导航点
.indicator(
//Indicator.digit() //数字导航指示效果
Indicator.dot() //原点导航点
.itemWidth(20) //未选中的宽度
.selectedItemWidth(20) //选中的宽度
.color(Color.Red) //未选中的颜色
.selectedColor(Color.Yellow) //选中的颜色
)