• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 创建轮播 (Swiper)
2
3
4[Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md)组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。
5
6针对复杂页面场景,可以使用 Swiper 组件的预加载机制,利用主线程的空闲时间来提前构建和布局绘制组件,优化滑动体验。<!--Del-->详细指导见[Swiper高性能开发指导](../performance/swiper_optimization.md)。<!--DelEnd-->
7
8
9## 布局与约束
10
11Swiper作为一个容器组件,如果设置了自身尺寸属性,则在轮播显示过程中均以该尺寸生效。如果自身尺寸属性未被设置,则分两种情况:如果设置了prevMargin或者nextMargin属性,则Swiper自身尺寸会跟随其父组件;如果未设置prevMargin或者nextMargin属性,则会自动根据子组件的大小设置自身的尺寸。
12
13
14## 循环播放
15
16通过loop属性控制是否循环播放,该属性默认值为true。
17
18当loop为true时,在显示第一页或最后一页时,可以继续往前切换到前一页或者往后切换到后一页。如果loop为false,则在第一页或最后一页时,无法继续向前或者向后切换页面。
19
20- loop为true
21
22```ts
23Swiper() {
24  Text('0')
25    .width('90%')
26    .height('100%')
27    .backgroundColor(Color.Gray)
28    .textAlign(TextAlign.Center)
29    .fontSize(30)
30
31  Text('1')
32    .width('90%')
33    .height('100%')
34    .backgroundColor(Color.Green)
35    .textAlign(TextAlign.Center)
36    .fontSize(30)
37
38  Text('2')
39    .width('90%')
40    .height('100%')
41    .backgroundColor(Color.Pink)
42    .textAlign(TextAlign.Center)
43    .fontSize(30)
44}
45.loop(true)
46```
47
48![loop_true](figures/loop_true.gif)
49
50- loop为false
51
52```ts
53Swiper() {
54  // ...
55}
56.loop(false)
57```
58
59![loop_false](figures/loop_false.gif)
60
61
62## 自动轮播
63
64Swiper通过设置autoPlay属性,控制是否自动轮播子组件。该属性默认值为false。
65
66autoPlay为true时,会自动切换播放子组件,子组件与子组件之间的播放间隔通过interval属性设置。interval属性默认值为3000,单位毫秒。
67
68```ts
69Swiper() {
70  // ...
71}
72.loop(true)
73.autoPlay(true)
74.interval(1000)
75```
76
77![autoPlay](figures/autoPlay.gif)
78
79
80## 导航点样式
81
82Swiper提供了默认的导航点样式和导航点箭头样式,导航点默认显示在Swiper下方居中位置,开发者也可以通过indicator属性自定义导航点的位置和样式,导航点箭头默认不显示。
83
84通过indicator属性,开发者可以设置导航点相对于Swiper组件上下左右四个方位的位置,同时也可以设置每个导航点的尺寸、颜色、蒙层和被选中导航点的颜色。
85
86- 导航点使用默认样式
87
88```ts
89Swiper() {
90  Text('0')
91    .width('90%')
92    .height('100%')
93    .backgroundColor(Color.Gray)
94    .textAlign(TextAlign.Center)
95    .fontSize(30)
96
97  Text('1')
98    .width('90%')
99    .height('100%')
100    .backgroundColor(Color.Green)
101    .textAlign(TextAlign.Center)
102    .fontSize(30)
103
104  Text('2')
105    .width('90%')
106    .height('100%')
107    .backgroundColor(Color.Pink)
108    .textAlign(TextAlign.Center)
109    .fontSize(30)
110}
111```
112
113![indicator](figures/indicator.PNG)
114
115- 自定义导航点样式
116
117导航点直径设为30vp,左边距为0,导航点颜色设为红色。
118
119```ts
120Swiper() {
121  // ...
122}
123.indicator(
124  Indicator.dot()
125    .left(0)
126    .itemWidth(15)
127    .itemHeight(15)
128    .selectedItemWidth(30)
129    .selectedItemHeight(15)
130    .color(Color.Red)
131    .selectedColor(Color.Blue)
132)
133```
134
135![ind](figures/ind.PNG)
136
137Swiper通过设置[displayArrow](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#displayarrow10)属性,可以控制导航点箭头的大小、位置、颜色,底板的大小及颜色,以及鼠标悬停时是否显示箭头。
138
139- 箭头使用默认样式
140
141```ts
142Swiper() {
143  // ...
144}
145.displayArrow(true, false)
146```
147
148![arrow1](figures/arrow1.gif)
149
150- 自定义箭头样式
151
152箭头显示在组件两侧,大小为18vp,导航点箭头颜色设为蓝色。
153
154```ts
155Swiper() {
156  // ...
157}
158.displayArrow({
159  showBackground: true,
160  isSidebarMiddle: true,
161  backgroundSize: 24,
162  backgroundColor: Color.White,
163  arrowSize: 18,
164  arrowColor: Color.Blue
165  }, false)
166```
167
168![arrow2](figures/arrow2.gif)
169
170## 页面切换方式
171
172Swiper支持手指滑动、点击导航点和通过控制器三种方式切换页面,以下示例展示通过控制器切换页面的方法。
173
174```ts
175@Entry
176@Component
177struct SwiperDemo {
178  private swiperBackgroundColors: Color[] = [Color.Blue, Color.Brown, Color.Gray, Color.Green, Color.Orange,
179    Color.Pink, Color.Red, Color.Yellow];
180  private swiperAnimationMode: (SwiperAnimationMode | boolean | undefined)[] = [undefined, true, false,
181    SwiperAnimationMode.NO_ANIMATION, SwiperAnimationMode.DEFAULT_ANIMATION, SwiperAnimationMode.FAST_ANIMATION];
182  private swiperController: SwiperController = new SwiperController();
183  private animationModeIndex: number = 0;
184  private animationMode: (SwiperAnimationMode | boolean | undefined) = undefined;
185  @State animationModeStr: string = 'undefined';
186  @State targetIndex: number = 0;
187
188  aboutToAppear(): void {
189    this.toSwiperAnimationModeStr();
190  }
191
192  build() {
193    Column({ space: 5 }) {
194      Swiper(this.swiperController) {
195        ForEach(this.swiperBackgroundColors, (backgroundColor: Color, index: number) => {
196          Text(index.toString())
197            .width(250)
198            .height(250)
199            .backgroundColor(backgroundColor)
200            .textAlign(TextAlign.Center)
201            .fontSize(30)
202        })
203      }
204      .indicator(true)
205
206      Row({ space: 12 }) {
207        Button('showNext')
208          .onClick(() => {
209            this.swiperController.showNext(); // 通过controller切换到后一页
210          })
211        Button('showPrevious')
212          .onClick(() => {
213            this.swiperController.showPrevious(); // 通过controller切换到前一页
214          })
215      }.margin(5)
216
217      Row({ space: 12 }) {
218        Text('Index:')
219        Button(this.targetIndex.toString())
220          .onClick(() => {
221            this.targetIndex = (this.targetIndex + 1) % this.swiperBackgroundColors.length;
222          })
223      }.margin(5)
224      Row({ space: 12 }) {
225        Text('AnimationMode:')
226        Button(this.animationModeStr)
227          .onClick(() => {
228            this.animationModeIndex = (this.animationModeIndex + 1) % this.swiperAnimationMode.length;
229            this.toSwiperAnimationModeStr();
230          })
231      }.margin(5)
232
233      Row({ space: 12 }) {
234        Button('changeIndex(' + this.targetIndex + ', ' + this.animationModeStr + ')')
235          .onClick(() => {
236            this.swiperController.changeIndex(this.targetIndex, this.animationMode); // 通过controller切换到指定页
237          })
238      }.margin(5)
239    }.width('100%')
240    .margin({ top: 5 })
241  }
242
243  private toSwiperAnimationModeStr() {
244    this.animationMode = this.swiperAnimationMode[this.animationModeIndex];
245    if ((this.animationMode === true) || (this.animationMode === false)) {
246      this.animationModeStr = '' + this.animationMode;
247    } else if ((this.animationMode === SwiperAnimationMode.NO_ANIMATION) ||
248      (this.animationMode === SwiperAnimationMode.DEFAULT_ANIMATION) ||
249      (this.animationMode === SwiperAnimationMode.FAST_ANIMATION)) {
250      this.animationModeStr = SwiperAnimationMode[this.animationMode];
251    } else {
252      this.animationModeStr = 'undefined';
253    }
254  }
255}
256```
257
258![controll](figures/controll.gif)
259
260
261## 轮播方向
262
263Swiper支持水平和垂直方向上进行轮播,主要通过vertical属性控制。
264
265当vertical为true时,表示在垂直方向上进行轮播;为false时,表示在水平方向上进行轮播。vertical默认值为false。
266
267
268- 设置水平方向上轮播。
269
270```ts
271Swiper() {
272  // ...
273}
274.indicator(true)
275.vertical(false)
276```
277
278
279![截图2](figures/截图2.PNG)
280
281
282- 设置垂直方向轮播。
283
284```ts
285Swiper() {
286  // ...
287}
288.indicator(true)
289.vertical(true)
290```
291
292
293![截图3](figures/截图3.PNG)
294
295
296## 每页显示多个子页面
297
298Swiper支持在一个页面内同时显示多个子组件,通过[displayCount](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#displaycount8)属性设置。
299
300```ts
301Swiper() {
302  Text('0')
303    .width(250)
304    .height(250)
305    .backgroundColor(Color.Gray)
306    .textAlign(TextAlign.Center)
307    .fontSize(30)
308  Text('1')
309    .width(250)
310    .height(250)
311    .backgroundColor(Color.Green)
312    .textAlign(TextAlign.Center)
313    .fontSize(30)
314  Text('2')
315    .width(250)
316    .height(250)
317    .backgroundColor(Color.Pink)
318    .textAlign(TextAlign.Center)
319    .fontSize(30)
320  Text('3')
321    .width(250)
322    .height(250)
323    .backgroundColor(Color.Blue)
324    .textAlign(TextAlign.Center)
325    .fontSize(30)
326}
327.indicator(true)
328.displayCount(2)
329```
330
331![two](figures/two.PNG)
332
333## 自定义切换动画
334
335Swiper支持通过[customContentTransition](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#customcontenttransition12)设置自定义切换动画,可以在回调中对视窗内所有页面逐帧设置透明度、缩放比例、位移、渲染层级等属性实现自定义切换动画。
336
337```ts
338@Entry
339@Component
340struct SwiperCustomAnimationExample {
341  private DISPLAY_COUNT: number = 2
342  private MIN_SCALE: number = 0.75
343
344  @State backgroundColors: Color[] = [Color.Green, Color.Blue, Color.Yellow, Color.Pink, Color.Gray, Color.Orange]
345  @State opacityList: number[] = []
346  @State scaleList: number[] = []
347  @State translateList: number[] = []
348  @State zIndexList: number[] = []
349
350  aboutToAppear(): void {
351    for (let i = 0; i < this.backgroundColors.length; i++) {
352      this.opacityList.push(1.0)
353      this.scaleList.push(1.0)
354      this.translateList.push(0.0)
355      this.zIndexList.push(0)
356    }
357  }
358
359  build() {
360    Column() {
361      Swiper() {
362        ForEach(this.backgroundColors, (backgroundColor: Color, index: number) => {
363          Text(index.toString()).width('100%').height('100%').fontSize(50).textAlign(TextAlign.Center)
364            .backgroundColor(backgroundColor)
365            .opacity(this.opacityList[index])
366            .scale({ x: this.scaleList[index], y: this.scaleList[index] })
367            .translate({ x: this.translateList[index] })
368            .zIndex(this.zIndexList[index])
369        })
370      }
371      .height(300)
372      .indicator(false)
373      .displayCount(this.DISPLAY_COUNT, true)
374      .customContentTransition({
375        timeout: 1000,
376        transition: (proxy: SwiperContentTransitionProxy) => {
377          if (proxy.position <= proxy.index % this.DISPLAY_COUNT || proxy.position >= this.DISPLAY_COUNT + proxy.index % this.DISPLAY_COUNT) {
378            // 同组页面完全滑出视窗外时,重置属性值
379            this.opacityList[proxy.index] = 1.0
380            this.scaleList[proxy.index] = 1.0
381            this.translateList[proxy.index] = 0.0
382            this.zIndexList[proxy.index] = 0
383          } else {
384            // 同组页面未滑出视窗外时,对同组中左右两个页面,逐帧根据position修改属性值
385            if (proxy.index % this.DISPLAY_COUNT === 0) {
386              this.opacityList[proxy.index] = 1 - proxy.position / this.DISPLAY_COUNT
387              this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position / this.DISPLAY_COUNT)
388              this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
389            } else {
390              this.opacityList[proxy.index] = 1 - (proxy.position - 1) / this.DISPLAY_COUNT
391              this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - (proxy.position - 1) / this.DISPLAY_COUNT)
392              this.translateList[proxy.index] = - (proxy.position - 1) * proxy.mainAxisLength - (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
393            }
394            this.zIndexList[proxy.index] = -1
395          }
396        }
397      })
398    }.width('100%')
399  }
400}
401```
402
403![customAnimation](figures/swiper-custom-animation.gif)
404
405## Swiper与Tabs联动
406
407Swiper选中的元素改变时,会通过onSelected回调事件,将元素的索引值index返回。通过调用tabsController.changeIndex(index)方法来实现Tabs页签的切换。
408
409```ts
410// xxx.ets
411class MyDataSource implements IDataSource {
412  private list: number[] = []
413
414  constructor(list: number[]) {
415    this.list = list
416  }
417
418  totalCount(): number {
419    return this.list.length
420  }
421
422  getData(index: number): number {
423    return this.list[index]
424  }
425
426  registerDataChangeListener(listener: DataChangeListener): void {
427  }
428
429  unregisterDataChangeListener() {
430  }
431}
432
433@Entry
434@Component
435struct TabsSwiperExample {
436  @State fontColor: string = '#182431'
437  @State selectedFontColor: string = '#007DFF'
438  @State currentIndex: number = 0
439  private list: number[] = []
440  private tabsController: TabsController = new TabsController()
441  private swiperController: SwiperController = new SwiperController()
442  private swiperData: MyDataSource = new MyDataSource([])
443
444  aboutToAppear(): void {
445    for (let i = 0; i <= 9; i++) {
446      this.list.push(i);
447    }
448    this.swiperData = new MyDataSource(this.list)
449  }
450
451  @Builder tabBuilder(index: number, name: string) {
452    Column() {
453      Text(name)
454        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
455        .fontSize(16)
456        .fontWeight(this.currentIndex === index ? 500 : 400)
457        .lineHeight(22)
458        .margin({ top: 17, bottom: 7 })
459      Divider()
460        .strokeWidth(2)
461        .color('#007DFF')
462        .opacity(this.currentIndex === index ? 1 : 0)
463    }.width('20%')
464  }
465
466  build() {
467    Column() {
468      Tabs({ barPosition: BarPosition.Start, controller: this.tabsController }) {
469        ForEach(this.list, (index: number) =>{
470          TabContent().tabBar(this.tabBuilder(index, '页签 ' + this.list[index]))
471        })
472      }
473      .onTabBarClick((index: number) => {
474        this.currentIndex = index
475        this.swiperController.changeIndex(index, true)
476      })
477      .barMode(BarMode.Scrollable)
478      .backgroundColor('#F1F3F5')
479      .height(56)
480      .width('100%')
481
482      Swiper(this.swiperController) {
483        LazyForEach(this.swiperData, (item: string) => {
484          Text(item.toString())
485            .onAppear(()=>{
486              console.info('onAppear ' + item.toString())
487            })
488            .onDisAppear(()=>{
489              console.info('onDisAppear ' + item.toString())
490            })
491            .width('100%')
492            .height('40%')
493            .backgroundColor(0xAFEEEE)
494            .textAlign(TextAlign.Center)
495            .fontSize(30)
496        }, (item: string) => item)
497      }
498      .loop(false)
499      .onSelected((index: number) => {
500        console.info("onSelected:" + index)
501        this.currentIndex = index;
502        this.tabsController.changeIndex(index)
503      })
504    }
505  }
506}
507```
508![Swiper与Tabs联动](figures/tabs_swiper.gif)
509
510## 设置圆点导航点间距
511
512针对圆点导航点,可以通过DotIndicator的space属性来设置圆点导航点的间距。
513
514```ts
515Swiper() {
516  // ...
517}
518.indicator(
519  new DotIndicator()
520    .space(LengthMetrics.vp(3))
521)
522```
523
524## 导航点忽略组件大小
525
526当导航点的bottom设为0之后,导航点的底部与Swiper的底部还会有一定间距。如果希望消除该间距,可通过调用bottom(bottom, ignoreSize)属性来进行设置。将ignoreSize 设置为true,即可忽略导航点组件大小,达到消除该间距的目的。
527
528- 圆点导航点忽略组件大小。
529
530```ts
531Swiper() {
532  // ...
533}
534.indicator(
535  new DotIndicator()
536    .bottom(LengthMetrics.vp(0), true)
537)
538```
539
540- 数字导航点忽略组件大小。
541
542```ts
543Swiper() {
544  // ...
545}
546.indicator(
547  new DigitIndicator()
548    .bottom(LengthMetrics.vp(0), true)
549)
550```
551
552圆点导航点设置间距及忽略组件大小完整示列代码如下:
553
554```ts
555import { LengthMetrics } from '@kit.ArkUI'
556
557// MyDataSource.ets
558class MyDataSource implements IDataSource {
559  private list: number[] = []
560
561  constructor(list: number[]) {
562    this.list = list
563  }
564
565  totalCount(): number {
566    return this.list.length
567  }
568
569  getData(index: number): number {
570    return this.list[index]
571  }
572
573  registerDataChangeListener(listener: DataChangeListener): void {
574  }
575
576  unregisterDataChangeListener() {
577  }
578}
579
580// SwiperExample.ets
581@Entry
582@Component
583struct SwiperExample {
584
585  @State space: LengthMetrics = LengthMetrics.vp(0)
586  @State spacePool: LengthMetrics[] = [LengthMetrics.vp(0), LengthMetrics.px(3), LengthMetrics.vp(10)]
587  @State spaceIndex: number = 0
588
589  @State ignoreSize: boolean = false
590  @State ignoreSizePool: boolean[] = [false, true]
591  @State ignoreSizeIndex: number = 0
592
593  private swiperController1: SwiperController = new SwiperController()
594  private data1: MyDataSource = new MyDataSource([])
595
596  aboutToAppear(): void {
597    let list1: number[] = []
598    for (let i = 1; i <= 10; i++) {
599      list1.push(i);
600    }
601    this.data1 = new MyDataSource(list1)
602  }
603
604  build() {
605    Scroll() {
606      Column({ space: 20 }) {
607        Swiper(this.swiperController1) {
608          LazyForEach(this.data1, (item: string) => {
609            Text(item.toString())
610              .width('90%')
611              .height(120)
612              .backgroundColor(0xAFEEEE)
613              .textAlign(TextAlign.Center)
614              .fontSize(30)
615          }, (item: string) => item)
616        }
617        .indicator(new DotIndicator()
618          .space(this.space)
619          .bottom(LengthMetrics.vp(0), this.ignoreSize)
620          .itemWidth(15)
621          .itemHeight(15)
622          .selectedItemWidth(15)
623          .selectedItemHeight(15)
624          .color(Color.Gray)
625          .selectedColor(Color.Blue))
626        .displayArrow({
627          showBackground: true,
628          isSidebarMiddle: true,
629          backgroundSize: 24,
630          backgroundColor: Color.White,
631          arrowSize: 18,
632          arrowColor: Color.Blue
633        }, false)
634
635        Column({ space: 4 }) {
636          Button('spaceIndex:' + this.spaceIndex).onClick(() => {
637            this.spaceIndex = (this.spaceIndex + 1) % this.spacePool.length;
638            this.space = this.spacePool[this.spaceIndex];
639          }).margin(10)
640
641          Button('ignoreSizeIndex:' + this.ignoreSizeIndex).onClick(() => {
642            this.ignoreSizeIndex = (this.ignoreSizeIndex + 1) % this.ignoreSizePool.length;
643            this.ignoreSize = this.ignoreSizePool[this.ignoreSizeIndex];
644          }).margin(10)
645        }.margin(2)
646      }.width('100%')
647    }
648  }
649}
650```
651
652![controll](figures/indicator_space.gif)
653
654## 相关实例
655
656针对Swiper组件开发,有以下相关实例可供参考:
657
658- [电子相册(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/ElectronicAlbum)
659
660- [Swiper的使用(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SwiperArkTS)
661<!--RP1--><!--RP1End-->