ArkUI-布局
轮播
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。
Swiper作为一个容器组件,如果设置了自身尺寸属性,则在轮播显示过程中均以该尺寸生效。如果自身尺寸属性未被设置,则分两种情况:如果设置了prevMargin
或者nextMargin
属性,则Swiper自身尺寸会跟随其父组件;如果未设置prevMargin
或者nextMargin
属性,则会自动根据子组件的大小设置自身的尺寸。
部分属性及方法
循环播放及自动轮播
通过loop
属性控制是否循环播放,该属性默认是true。
通过autoPlay
属性控制是否自动轮播子组件,该属性默认为false。
通过interval
属性控制轮播时间间隔,该属性默认值为3000毫秒。
Swiper() {
// ...
}
.loop(true)
.autoPlay(true)
.interval(1000)
导航点样式
Swiper提供了默认的导航点,显示在Swiper组件的正下方。
可以通过indicator
属性,自定义导航点的位置(Swiper组件的上下左右四个方向)、尺寸、颜色、蒙层、选中颜色。
Swiper() {
// ...
}
.indicator(
Indicator.dot()
.left(0)//导航点位置
.itemWidth(15)//导航点宽度
.itemHeight(15)//导航点高度
.selectedItemWidth(30)//选中导航点宽度
.selectedItemHeight(15)//选中导航点高度
.color(Color.Red)//导航点颜色
.selectedColor(Color.Blue)//导航点选中颜色
).displayArrow({
showBackground: true,//是否显示导航箭头背景
isSidebarMiddle: true,//导航箭头位置
backgroundSize: 24,//导航箭头背景尺寸
backgroundColor: Color.White,//导航箭头背景颜色
arrowSize: 18,//导航箭头尺寸
arrowColor: Color.Blue//导航箭头颜色
}, false)
页面切换方式
控制器切换页面
@Entry
@Component
struct SwiperDemo {
private swiperController: SwiperController = new SwiperController();
build() {
Column({ space: 5 }) {
Swiper(this.swiperController) {
}
.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 })
}
}
轮播方向
Swiper支持水平和垂直方向上进行轮播,主要通过vertical属性控制。
当vertical为true时,表示在垂直方向上进行轮播;为false时,表示在水平方向上进行轮播。vertical默认值为false。
Swiper() {
// ...
}
.indicator(true)
.vertical(true)
每页显示多个子页面
Swiper支持在一个页面内同时显示多个子组件,通过displayCount属性设置。
Swiper() {
// ...
}
.indicator(true)
.displayCount(2)
自定义切换动画
Swiper支持通过customContentTransition
设置自定义切换动画,可以在回调中对视窗内所有页面逐帧设置透明度、缩放比例、位移、渲染层级等属性实现自定义切换动画。
@Entry
@Component
struct SwiperCustomAnimationExample {
private DISPLAY_COUNT: number = 2
private MIN_SCALE: number = 0.75
@State backgroundColors: Color[] = [Color.Green, Color.Blue, Color.Yellow, Color.Pink, Color.Gray, Color.Orange]
@State opacityList: number[] = []
@State scaleList: number[] = []
@State translateList: number[] = []
@State zIndexList: number[] = []
aboutToAppear(): void {
for (let i = 0; i < this.backgroundColors.length; i++) {
this.opacityList.push(1.0)
this.scaleList.push(1.0)
this.translateList.push(0.0)
this.zIndexList.push(0)
}
}
build() {
Column() {
Swiper() {
ForEach(this.backgroundColors, (backgroundColor: Color, index: number) => {
Text(index.toString()).width('100%').height('100%').fontSize(50).textAlign(TextAlign.Center)
.backgroundColor(backgroundColor)
.opacity(this.opacityList[index])
.scale({ x: this.scaleList[index], y: this.scaleList[index] })
.translate({ x: this.translateList[index] })
.zIndex(this.zIndexList[index])
})
}
.height(300)
.indicator(false)
.displayCount(this.DISPLAY_COUNT, true)
.customContentTransition({
timeout: 1000,
transition: (proxy: SwiperContentTransitionProxy) => {
if (proxy.position <= proxy.index % this.DISPLAY_COUNT || proxy.position >= this.DISPLAY_COUNT + proxy.index % this.DISPLAY_COUNT) {
// 同组页面完全滑出视窗外时,重置属性值
this.opacityList[proxy.index] = 1.0
this.scaleList[proxy.index] = 1.0
this.translateList[proxy.index] = 0.0
this.zIndexList[proxy.index] = 0
} else {
// 同组页面未滑出视窗外时,对同组中左右两个页面,逐帧根据position修改属性值
if (proxy.index % this.DISPLAY_COUNT === 0) {
this.opacityList[proxy.index] = 1 - proxy.position / this.DISPLAY_COUNT
this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position / this.DISPLAY_COUNT)
this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
} else {
this.opacityList[proxy.index] = 1 - (proxy.position - 1) / this.DISPLAY_COUNT
this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - (proxy.position - 1) / this.DISPLAY_COUNT)
this.translateList[proxy.index] = - (proxy.position - 1) * proxy.mainAxisLength - (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
}
this.zIndexList[proxy.index] = -1
}
}
})
}.width('100%')
}
}
选项卡
Tabs
组件可以在一个页面内快速实现视图内容的切换。
Tabs组件的页面组成包含两个部分,分别是TabContent
和TabBar
,页面结构如下:
TabContent组件不支持设置宽度和高度,其宽度默认撑满Tabs父组件,高度由Tabs父组件高度和TabBar组件高度共同决定。
使用方式
Tabs() {
TabContent() {
Text('首页的内容').fontSize(30)
}
.tabBar('首页')
TabContent() {
Text('推荐的内容').fontSize(30)
}
.tabBar('推荐')
TabContent() {
Text('发现的内容').fontSize(30)
}
.tabBar('发现')
TabContent() {
Text('我的内容').fontSize(30)
}
.tabBar("我的")
}
部分属性及方法
顶部导航和底部导航
Tabs组件的barPosition
参数可以控制tabBar的位置。
BarPosition.Start:导航栏在顶部。
BarPosition.End:导航栏在底部。
Tabs({ barPosition: BarPosition.End }) {
}
侧边导航
侧边导航使用barPosition
参数搭配vertical
方法进行控制。
Tabs({ barPosition: BarPosition.Start }) {
...
}
.vertical(true)
.barWidth(100)
.barHeight(200)
限制导航栏的滑动
控制滑动切换的属性为scrollable,默认值为true,表示可以滑动,若要限制滑动切换页签则需要设置为false。
Tabs({ barPosition: BarPosition.End }) {
TabContent(){
Column(){
Tabs(){
// 顶部导航栏内容
...
}
}
.backgroundColor('#ff08a8f1')
.width('100%')
}
.tabBar('首页')
// 其他TabContent内容:发现、推荐、我的
...
}
.scrollable(false)
固定导航栏和滚动导航栏
Tabs组件通过barMode
属性控制导航栏是否可以滚动。
- BarMode.Fixed:固定导航栏,不可滚动,内容均分tabBar的宽度。
- BarMode.Scrollable:滚动导航栏。
自定义导航栏
使用TabContent的tabBar方法构建时可以自定义tabBar。
传入参数包括页签文字title,对应位置index,以及选中状态和未选中状态的图片资源
@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)
}
TabContent() {
Column(){
Text('我的内容')
}
.width('100%')
.height('100%')
.backgroundColor('#007DFF')
}
.tabBar(this.tabBuilder('我的', 0, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
切换至指定页签
在不使用自定义导航栏时,默认的Tabs会实现切换逻辑。在使用了自定义导航栏后,默认的Tabs仅实现滑动内容页和点击页签时内容页的切换逻辑,页签切换逻辑需要自行实现。即用户滑动内容页和点击页签时,页签栏需要同步切换至内容页对应的页签。
@Entry
@Component
struct TabsExample1 {
@State currentIndex: number = 2
@Builder tabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
}
}
build() {
Column() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
...
}.tabBar(this.tabBuilder('首页', 0))
TabContent() {
...
}.tabBar(this.tabBuilder('发现', 1))
TabContent() {
...
}.tabBar(this.tabBuilder('推荐', 2))
TabContent() {
...
}.tabBar(this.tabBuilder('我的', 3))
}
.animationDuration(0)
.backgroundColor('#F1F3F5')
.onChange((index: number) => {
this.currentIndex = index
})
}.width('100%')
}
}