6.轮播(Swiper)
1.概述
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。
针对复杂页面场景,可以使用 Swiper 组件的预加载机制,利用主线程的空闲时间来提前构建和布局绘制组件,优化滑动体验。
布局与约束
Swiper作为一个容器组件,如果设置了自身尺寸属性,则在轮播显示过程中均以该尺寸生效。如果自身尺寸属性未被设置,则分两种情况:如果设置了prevMargin或者nextMargin属性,则Swiper自身尺寸会跟随其父组件;如果未设置prevMargin或者nextMargin属性,则会自动根据子组件的大小设置自身的尺寸。0
loop |
boolean |
是否开启循环。设置为true时表示开启循环, |
autoPlay |
boolean |
子组件是否自动播放。默认值:false说明:loop为false时,自动轮播到最后一页时停止轮播。手势切换后不是最后一页时继续播放。 |
interval |
number |
使用自动播放时播放的时间间隔,单位为毫秒。默认值:3000 |
vertical |
boolean |
是否为纵向滑动。默认值:false |
indicator |
Indicator.dot()/digit() |
导航点样式/导航数字样式 |
2.循环播放
通过loop属性控制是否循环播放,该属性默认值为true。
当loop为true时,在显示第一页或最后一页时,可以继续往前切换到前一页或者往后切换到后一页。如果loop为false,则在第一页或最后一页时,无法继续向前或者向后切换页面。
- loop为true
Swiper() {
Text('0')
.width('90%')
.height('100%')
.backgroundColor(Color.Gray)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('1')
.width('90%')
.height('100%')
.backgroundColor(Color.Green)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('2')
.width('90%')
.height('100%')
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.fontSize(30)
}
.loop(true)
-
- loop为false
Swiper() {
// ...
}
.loop(false)
3.导航样式
Swiper提供了默认的导航点样式和导航点箭头样式,导航点默认显示在Swiper下方居中位置,开发者也可以通过indicator属性自定义导航点的位置和样式,导航点箭头默认不显示。
通过indicator属性,开发者可以设置导航点相对于Swiper组件上下左右四个方位的位置,同时也可以设置每个导航点的尺寸、颜色、蒙层和被选中导航点的颜色。
自定义导航点样式
Swiper() {
// ...
}
.indicator(
Indicator.dot()
.left(0)
.itemWidth(15)
.itemHeight(15)
.selectedItemWidth(30)
.selectedItemHeight(15)
.color(Color.Red)
.selectedColor(Color.Blue)
)
Swiper通过设置displayArrow属性,可以控制导航点箭头的大小、位置、颜色,底板的大小及颜色,以及鼠标悬停时是否显示箭头。
箭头使用默认样式
Swiper() {
// ...
}
.displayArrow(true, false)
- 自定义箭头样式
Swiper() {
// ...
}
.displayArrow({
showBackground: true,//设置箭头底板是否显示。
isSidebarMiddle: true,//设置箭头显示位置。
backgroundSize: 24,//设置底板大小。
backgroundColor: Color.White,//设置底板颜色,在导航点两侧显示
arrowSize: 18,//设置箭头大小
arrowColor: Color.Blue//箭头颜色
}, false)
4.控制器切换
@Entry
@Component
struct SwiperDemo {
private swiperController: SwiperController = new SwiperController();
build() {
Column({ space: 5 }) {
Swiper(this.swiperController) {
Text('0')
.width(250)
.height(250)
.backgroundColor(Color.Gray)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('1')
.width(250)
.height(250)
.backgroundColor(Color.Green)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('2')
.width(250)
.height(250)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.fontSize(30)
}
.indicator(true)
Row({ space: 12 }) {
Button('showNext')
.onClick(() => {
this.swiperController.showNext(); // 通过controller切换到后一页
})
Button('showPrevious')
.onClick(() => {
this.swiperController.showPrevious(); // 通过controller切换到前一页
})
}.margin(5)
}.width('100%')
.margin({ top: 5 })
}
}
5.轮播方向
Swiper支持水平和垂直方向上进行轮播,主要通过vertical属性控制。
当vertical为true时,表示在垂直方向上进行轮播;为false时,表示在水平方向上进行轮播。vertical默认值为false。
Swiper() {
// ...
}
.indicator(true)
.vertical(false)
Swiper() {
// ...
}
.indicator(true)
.vertical(true)
6.每页显示多个子页面
Swiper支持在一个页面内同时显示多个子组件,通过displayCount属性设置。
Swiper() {
Text('0')
.width(250)
.height(250)
.backgroundColor(Color.Gray)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('1')
.width(250)
.height(250)
.backgroundColor(Color.Green)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('2')
.width(250)
.height(250)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.fontSize(30)
Text('3')
.width(250)
.height(250)
.backgroundColor(Color.Blue)
.textAlign(TextAlign.Center)
.fontSize(30)
}
.indicator(true)
.displayCount(2)
7.列表(list)🎈
1.概述
列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
使用列表可以轻松高效地显示结构化、可滚动的信息。通过在List组件中按垂直或者水平方向线性排列子组件ListItemGroup或ListItem,为列表中的行或列提供单个视图,或使用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件
2.布局与约束
图1 List、ListItemGroup和ListItem组件关系
说明
List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。
3.开发布局
设置主轴方向
List组件主轴默认是垂直方向,即默认情况下不需要手动设置List方向,就可以构建一个垂直滚动列表。
若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。
List() {
// ...
}
.listDirection(Axis.Horizontal)
设置交叉轴布局
List组件的交叉轴布局可以通过lanes和alignListItem属性进行设置,lanes属性用于确定交叉轴排列的列表项数量,alignListItem用于设置子组件在交叉轴方向的对齐方式。
List组件的lanes属性通常用于在不同尺寸的设备自适应构建不同行数或列数的列表,即一次开发、多端部署的场景,例如歌单列表。lanes属性的取值类型是"number | LengthConstrain",即整数或者LengthConstrain类型。以垂直列表为例,如果将lanes属性设为2,表示构建的是一个两列的垂直列表,如图2中右图所示。lanes的默认值为1,即默认情况下,垂直列表的列数是1。
List() {
// ...
}
.lanes(2)
示例
如上图所示,联系人列表的列表项中,每个联系人都有头像和名称。此时,需要将Image和Text封装到一个Row容器内。
List() {
ListItem() {
Row() {
Image($r('app.media.iconE'))
.width(40)
.height(40)
.margin(10)
Text('小明')
.fontSize(20)
}
}
ListItem() {
Row() {
Image($r('app.media.iconF'))
.width(40)
.height(40)
.margin(10)
Text('小红')
.fontSize(20)
}
}
}
4.自定义布局
设置内容间距
在初始化列表时,如需在列表项之间添加间距,可以使用space参数。例如,在每个列表项之间沿主轴方向添加10vp的间距:
List({ space: 10 }) {
// ...
}
添加分隔线
分隔线用来将界面元素隔开,使单个元素更加容易识别。如下图所示,当列表项左边有图标(如蓝牙图标),由于图标本身就能很好的区分,此时分隔线从图标之后开始显示即可。
图9 设置列表分隔线样式
List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。
startMargin和endMargin属性分别用于设置分隔线距离列表侧边起始端的距离和距离列表侧边结束端的距离。
class DividerTmp {
strokeWidth: Length = 1
startMargin: Length = 60
endMargin: Length = 10
color: ResourceColor = '#ffe9f0f0'
constructor(strokeWidth: Length, startMargin: Length, endMargin: Length, color: ResourceColor) {
this.strokeWidth = strokeWidth
this.startMargin = startMargin
this.endMargin = endMargin
this.color = color
}
}
@Entry
@Component
struct EgDivider {
@State egDivider: DividerTmp = new DividerTmp(1, 60, 10, '#ffe9f0f0')
build() {
List() {
// ...
}
.divider(this.egDivider)
}
}
此示例表示从距离列表侧边起始端60vp开始到距离结束端10vp的位置,画一条粗细为1vp的分割线,可以实现 上 图设置列表分隔线的样式。
说明
- 分隔线的宽度会使ListItem之间存在一定间隔,当List设置的内容间距小于分隔线宽度时,ListItem之间的间隔会使用分隔线的宽度。
- 当List存在多列时,分割线的startMargin和endMargin作用于每一列上。
- List组件的分隔线画在两个ListItem之间,第一个ListItem上方和最后一个ListItem下方不会绘制分隔线。
添加滚动条
当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条,如下图所示。
图10 列表的滚动条
在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为BarState,当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不进行任何操作,2秒后滚动条自动消失。
scrollBar属性API version 9及以下版本默认值为BarState.Off,从API version 10版本开始默认值为BarState.Auto。
List() {
// ...
}
.scrollBar(BarState.Auto)
List的触底事件 .onReachEnd()
作用:可以实现List列表数据触底时加载
@Entry
@Component
struct Index {
@State isLoding: boolean = false
build() {
Column() {
List({ space: 5 }) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8], () => {
ListItem() {
Row() {
}.height(100).width('100%').backgroundColor(Color.Pink)
}
})
// 加一个正在加载中的动画
ListItem() {
LoadingProgress()
.height(50)
}.width('100%')
}
// onReachEnd当List的滚动条滚动到底部的时候会触发
// ✨✨问题:鼠标一抖动触发了
/* 解决方案:设置一个状态变量初始值为false,
在onReachEnd中加一个if判断 -> 方法体里面将这个变量的值设置为true,
直到服务器返回了数据后,
重置这个变量的值为false
✨✨onReachEnd特点:页面加载的时候从服务器获取到数据之后会自动触发
*/
.onReachEnd(() => {
// 我们可以发请求拿新数据
if (this.isLoding == false) {
this.isLoding = true
setTimeout(() => {
AlertDialog.show({ message: '触底加载' })
this.isLoding = false
}, 3000)
}
})
}
.height('100%')
.width('100%')
}
}
注意:onReachEnd方法在页面进入的时候会触发一次 ,所以注释aboutToAppear中的数据请求调用
List拉到最下时会出现回弹效果,这样会触发两次数据加载,可以在List上增加
.edgeEffect(EdgeEffect.None) 来关闭回弹效果解决
ListItem 实现左滑右滑事件(swipeAction)
可以实现子组件左滑右滑
import axios,{AxiosResponse,AxiosError} from '@ohos/axios'
import { iBookResponse,iBookInfo } from './Data'
const req = axios.create({
baseURL:'https://hmajax.itheima.net/api/'
})
@Entry
@Component
struct Index {
// 1. 我的书架
@Builder
MyBook() {
Row() {
Image($r('app.media.ic_public_drawer_filled'))
.height(20)
Text('我的书架')
.fontSize(20)
.fontWeight(800)
Image($r('app.media.ic_public_add'))
.height(20)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(10)
.border({ width: { bottom: 1 }, color: { bottom: 'rgba(0,0,0,0.2)' } })
}
// 2. 书籍列表
@Builder
BookList() {
ForEach(this.data, (item: iBookInfo, index: number) => {
List() {
ListItem() {
// 布局
Row({ space: 10 }) {
Image($r('app.media.ic_public_cover'))
.width(100)
Column({ space: 25 }) {
Column() {
Text('书名:'+item.bookname)
.width('100%')
.fontSize(20)
.fontWeight(800)
Text('作者:'+item.author)
.width('100%')
.fontSize(14)
.fontWeight(600)
.fontColor('rgba(0,0,0,0.4)')
.padding({ top: 5 })
}
Text('出版社:'+item.publisher)
.width('100%')
.fontWeight(600)
.fontColor('rgba(0,0,0,0.4)')
}
.layoutWeight(1)
}
.padding(10)
}
.swipeAction({
end: this.delBuilder(),
edgeEffect:SwipeEdgeEffect.Spring
})
}
})
}
// 3. 删除书籍的构建函数
@Builder
delBuilder() {
Column(){
Text('删除')
.backgroundColor(Color.Black)
.fontWeight(700)
.fontColor(Color.White)
.height('100%')
.width(90)
.textAlign(TextAlign.Center)
}
.padding(10)
}
@State data:iBookInfo[] = []
aboutToAppear(): void {
this.getList()
}
async getList(){
try{
let res:AxiosResponse<iBookResponse> = await req({
url:'books',
params:{
creator:'zs111'
}
})
// AlertDialog.show({ message:JSON.stringify(res.data,null,2) })
this.data = res.data.data
}catch (err){
const error:AxiosError = err as AxiosError
AlertDialog.show({ message:JSON.stringify(error,null,2) })
}
}
build() {
Column() {
// 1. 我的书架
this.MyBook()
// 2. 书籍列表
this.BookList()
}
.height('100%')
.width('100%')
}
}
8.选项卡(Tabs)
1.概述
当页面信息较多时,为了让用户能够聚焦于当前显示的内容,需要对页面内容进行分类,提高页面空间利用率。Tabs组件可以在一个页面内快速实现视图内容的切换,一方面提升查找信息的效率,另一方面精简用户单次获取到的信息量。
基本布局
Tabs组件的页面组成包含两个部分,分别是TabContent和TabBar。TabContent是内容页,TabBar是导航页签栏,页面结构如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。
图1 Tabs组件布局示意图
说明
- TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
- TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。
2.常用属性
默认的 tabs 已经可以实现切换,接下来咱们来看看如何通过属性控制它
Tabs常用属性 :
●vertical 属性即可调整导航为 水平 或 垂直
●barPosition 即可调整导航位置为 开头 或 结尾
●scrollable 即可调整是否允许 滑动切换
●animationDuration 设置动画时间 毫秒
// BarPosition.Start 起始
// BarPosition.End 结尾
Tabs() {
// 内容略
}
.vertical(true)// 垂直导航 true / 水平false
.scrollable(true) // 允许滑动 true / 不允许 false
.animationDuration(0) // 切换动画的时间,毫秒
.barPosition(BarPosition.End)
3.滚动导航栏
如何导航栏的内容较多,屏幕无法容纳时,可以将他设置为滚动
通过 Tabs 组件的 barMode 属性即可调整 固定导航栏 或 滚动导航栏
Tabs(){
// 内容略
}
.barMode(BarMode.Scrollable)// 滚动
// .barMode(BarMode.Fixed)// 默认值
4.自定义 tabBar
如果要更改tabBar默认的显示外观,就需要 自定义tabBar
自定义tabBar核心代码
- 声明自定义builder函数
- .tabBar()中使用自定义builder函数
5.事件
用户在切换tabs组件内容时,有如下两个事件都可以获取到当前页面的索引
我们来使用一下,为下一步做高亮切换效果做准备
名称 |
功能描述 |
onChange(event: (index: number) => void) (要等过渡动画渲染完之后才会触发,而且不太稳定) |
Tab页签切换后触发的事件。 - index:当前显示的index索引,索引从0开始计算 滑动切换、点击切换 均会触发 |
onTabBarClick(event: (index: number) => void)10+(用户一点击 tabBar 就触发回调函数) |
Tab页签点击后触发的事件。 - index:被点击的index索引,索引从0开始计算 |
9. 创建网格 (Grid/GridItem)
1. 概述
网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等。
ArkUI提供了Grid容器组件和子组件GridItem,用于构建网格布局。Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。Grid组件支持使用条件渲染、循环渲染、懒加载等方式生成子组件。
2. 布局与约束
Grid组件为网格容器,其中容器内各条目对应一个GridItem组件,如下图所示。
图1 Grid与GridItem组件关系
说明
Grid的子组件必须是GridItem组件。
网格布局是一种二维布局。Grid组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行或者几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件以及间距会等比例调整,从而实现网格布局的自适应能力。根据Grid的这些布局能力,可以构建出不同样式的网格布局,如下图所示。
图2 网格布局
如果Grid组件设置了宽高属性,则其尺寸为设置值。如果没有设置宽高属性,Grid组件的尺寸默认适应其父组件的尺寸。
Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况:
- 行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐使用该种布局方式)
- 只设置行、列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动的方式展示。
- 行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。
3.使用
// xxx.ets
@Entry
@Component
struct GridExample {
@State numbers1: String[] = ['0', '1', '2', '3', '4']
@State numbers2: String[] = ['0', '1','2','3','4','5']
layoutOptions3: GridLayoutOptions = {
// 大小规则的GridItem在Grid中占的行数和列数,只支持占1行1列即[1, 1]。
regularSize: [1, 1],
// 设置指定索引index对应的GridItem的位置及大小[rowStart,columnStart,rowSpan,columnSpan]。
// 其中rowStart为行起始位置,columnStart为列起始位置,无单位。
onGetRectByIndex: (index: number) => {
if (index == 0)
return [0, 0, 1, 1]
else if(index==1)
return [0, 1, 2, 2]
else if(index==2)
return [0 ,3 ,3 ,3]
else if(index==3)
return [3, 0, 3, 3]
else if(index==4)
return [4, 3, 2, 2]
else
return [5, 5, 1, 1]
}
}
build() {
Column({ space: 5 }) {
Grid() {
ForEach(this.numbers1, (day: string) => {
ForEach(this.numbers1, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor(0xF9CF93)
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
}, (day: string) => day)
}, (day: string) => day)
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('90%')
.backgroundColor(0xFAEEE0)
.height(300)
Text('GridLayoutOptions的使用:onGetRectByIndex。').fontColor(Color.Red).fontSize(14).width('90%')
Grid(undefined, this.layoutOptions3) {
ForEach(this.numbers2, (day: string) => {
GridItem() {
Text(day)
.fontSize(16)
.backgroundColor(0xF9CF93)
.width('100%')
.height("100%")
.textAlign(TextAlign.Center)
}
.height("100%")
.width('100%')
}, (day: string) => day)
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('90%')
.backgroundColor(0xFAEEE0)
.height(300)
}.width('100%').margin({ top: 5 })
}
}