开天辟地 HarmonyOS(鸿蒙) - 组件(列表类): Swiper(组件轮播列表)

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 组件(列表类): Swiper(组件轮播列表)

示例如下:

pages\component\list\SwiperDemo.ets

/*
 * Swiper - 组件轮播列表
 *
 * 注:
 * 1、Swiper 结合 ForEach 的应用可以参考 /component/list/ListDemo5.ets 中的说明(但是不支持通过 ForEach 的 onMove() 拖动排序)
 * 2、Swiper 结合 LazyForEach 的应用可以参考 /component/list/ListDemo6.ets 中的说明
 * 3、Swiper 结合 Repeat 的应用可以参考 /component/list/ListDemo7.ets 中的说明
 */

import { MyLog, TitleBar } from '../../TitleBar';

@Entry
@Component
struct SwiperDemo {

  build() {
    Column() {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('基础1').align(Alignment.Top)
        TabContent() { MySample2() }.tabBar('基础2').align(Alignment.Top)
        TabContent() { MySample3() }.tabBar('controller 和 event').align(Alignment.Top)
        TabContent() { MySample4() }.tabBar('自定义切换动画').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

class MyDataSource implements IDataSource {
  private array: string[] = [];
  constructor(array: string[]) {
    this.array = array
  }
  public totalCount(): number {
    return this.array.length;
  }
  public getData(index: number): string {
    return this.array[index];
  }
  registerDataChangeListener(listener: DataChangeListener): void { }
  unregisterDataChangeListener() { }
}

@Component
struct MySample1 {

  private array: MyDataSource = new MyDataSource(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19']);

  build() {
    Column({ space: 10 }) {

      /*
       * Swiper - 组件轮播列表
       *   cachedCount() - 指定预挂载的 item 的数量(参见 /component/list/ListDemo6.ets 中的说明)
       *   vertical() - 是否在垂直方向上轮播
       *   index() - 当前显示的子组件的索引位置
       *   itemSpace() - 子组件之间的间距
       *   autoPlay() - 是否自动播放
       *   interval()- 翻页的间隔时间
       *   curve() - 组件之间切换时的动画曲线(参见 /animation/CurveDemo.ets 中的说明)
       *   duration() - 组件之间切换时的动画时长,必须指定 curve() 后才会生效
       *   loop() - 是否支持循环播放(即最右边时再往右则播最左边的,最左边的再往左则播最右边的)
       *   disableSwipe() - 是否禁用通过手势滑动切换
       *   edgeEffect() - 当禁用循环播放时,滚动到边缘后的效果
       *     edgeEffect - 效果(EdgeEffect 枚举)
       *       Spring - 弹性效果
       *       Fade - 圆弧阴影效果
       *       None - 无效果
       *   indicatorInteractive() - 是否可以通过导航小圆点翻页
       *   indicator() - 导航小圆点的样式
       *     一个 boolean 值,设置为 false 则不显示导航小圆点
       *     一个 DotIndicator 对象,指定导航小圆点的样式
       *       top(), right(), bottom(), left() - 小圆点的位置
       *       itemWidth(), itemHeight(), color() - 小圆点的宽高和颜色
       *       selectedItemWidth(), selectedItemHeight(), selectedColor() - 小圆点被选中时的宽高和颜色
       *       maxDisplayCount() - 显示的小圆点的最大数
       *       mask() - 是否为小圆点显示一个半透明背景
       *     一个 DigitIndicator 对象,导航小圆点改为导航页码,并指定其样式
       *       top(), right(), bottom(), left() - 页码的位置
       *       fontColor(), digitFont() - 页码的颜色和字体样式
       *       selectedFontColor(), selectedDigitFont() - 当前页码的颜色和字体样式
       *   displayArrow() - 导航箭头的样式
       *     value - 设置为 false 则隐藏,设置为 ArrowStyle 对象则设置箭头的样式
       *       showBackground - 是否显示箭头的圆形背景
       *       backgroundSize - 箭头圆形背景的大小
       *       backgroundColor - 箭头圆形背景的颜色
       *       arrowSize - 箭头的大小
       *       arrowColor - 箭头的颜色
       *       isSidebarMiddle - 箭头是否显示在中部的左右两侧(设置为 false 则显示在导航小圆点的两侧)
       *     isHoverShow - 是否只有鼠标悬停时才显示箭头(设置为 false 则一直会显示箭头)
       */

      Swiper() {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Color.Orange).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
        }, (item: string) => item)
      }
      .cachedCount(2)
      .vertical(false)
      .itemSpace(0)
      .index(13)
      .autoPlay(true)
      .interval(3000)
      .curve(Curve.Linear)
      .duration(1000)
      .loop(true)
      .disableSwipe(false)
      .effectMode(EdgeEffect.Spring)
      .indicatorInteractive(true)
      .indicator(
        new DotIndicator()
          .itemWidth(14)
          .itemHeight(14)
          .selectedItemWidth(16)
          .selectedItemHeight(16)
          .color(Color.Red)
          .selectedColor(Color.Green)
          .maxDisplayCount(6)
          .mask(true)
      )
      .displayArrow({
        showBackground: true,
        backgroundSize: 24,
        backgroundColor: Color.White,
        arrowSize: 18,
        arrowColor: Color.Blue,
        isSidebarMiddle: true,
      }, false)

      Swiper() {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Color.Orange).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
        }, (item: string) => item)
      }
      .indicatorInteractive(true)
      .indicator(
        new DigitIndicator()
          .left(0)
          .bottom(0)
          .fontColor(Color.Red)
          .digitFont({ size: 20, weight: FontWeight.Normal })
          .selectedFontColor(Color.Green)
          .selectedDigitFont({ size: 20, weight: FontWeight.Bold })
      )

      Swiper() {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Color.Orange).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
        }, (item: string) => item)
      }
      .displayCount(5, true)
    }
  }
}

@Component
struct MySample2 {

  private array: MyDataSource = new MyDataSource(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19']);

  build() {
    Column({ space: 10 }) {

      /*
       * Swiper - 组件轮播列表
       *   displayCount() - 每页显示的子组件数量,以及翻页模式
       *     value - 每页显示的子组件数量
       *     swipeByGroup - 翻页模式
       *       true - 按组翻页,每次翻页会翻 value 指定的数量
       *       false - 按组件翻页,每次翻页会翻一个组件
       *   prevMargin() - 当前主要显示的子组件的左侧子组件显示出来的宽度
       *   nextMargin() - 当前主要显示的子组件的右侧子组件显示出来的宽度
       *   nestedScroll() - 把 Swiper 放到一个可滚动组件内时的嵌套滚动逻辑(SwiperNestedScrollMode 枚举)
       *     SELF_ONLY - 在 Swiper 中滚的话只能滚 Swiper 自己
       *     SELF_FIRST - 在 Swiper 中滚的话,如果禁用了循环播放,且滚到边缘后,就会滚父组件
       */

      Swiper() {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Math.floor(Math.random() * (0xffffff + 1))).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
        }, (item: string) => item)
      }
      .displayCount(5, true)

      Swiper() {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Math.floor(Math.random() * (0xffffff + 1))).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
        }, (item: string) => item)
      }
      .nextMargin(50)
      .prevMargin(50)

      List({ space: 20 }) {
        ListItem() {
          Column().backgroundColor(Color.Red).width(300).height(150)
        }
        ListItem() {
          Swiper() {
            LazyForEach(this.array, (item: string) => {
              Text(item).backgroundColor(Math.floor(Math.random() * (0xffffff + 1))).fontColor(Color.White)
                .fontSize(24).textAlign(TextAlign.Center).height(150).width('100%')
            }, (item: string) => item)
          }
          .loop(false)
          .nestedScroll(SwiperNestedScrollMode.SELF_FIRST)
        }
        ListItem() {
          Column().backgroundColor(Color.Green).width(300).height(150)
        }
      }
      .listDirection(Axis.Horizontal)
    }
  }
}

@Component
struct MySample3 {

  // SwiperController - 用于和绑定的 Swiper 之间的交互
  private swiperController: SwiperController = new SwiperController()
  private array: MyDataSource = new MyDataSource(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19']);
  @State message1: string = ""
  @State message2: string = ""
  @State message3: string = ""
  @State message4: string = ""

  build() {
    Column({ space: 5 }) {

      /*
       * Swiper - 组件轮播列表
       *   controller - 绑定的 SwiperController 对象
       *   onChange() - 当前显示的子组件的索引位置发生变化时的回调
       *   onGestureSwipe() - 跟手滑动时的回调
       *     index - 跟手的子组件的索引位置
       *     extraInfo.currentOffset - 跟手的子组件的偏移距离
       *   onAnimationStart() - 切换动画开始时的回调
       *     index - 当前显示的子组件的索引位置
       *     targetIndex - 切换动画的需要显示的目标子组件的索引位置
       *     extraInfo.currentOffset - 切换动画开始时,当前显示的子组件的偏移距离
       *     extraInfo.targetOffset - 切换动画开始时,切换动画的需要显示的目标子组件的偏移距离
       *     extraInfo.velocity - 手势触发切换动画时的离手速度
       *   onAnimationEnd() - 切换动画结束时的回调
       *     index - 当前显示的子组件的索引位置
       *   onContentDidScroll() - 页面滚动时的回调
       *     selectedIndex - 启动滚动的子组件的索引位置
       *     index - 可视区内的子组件的索引位置
       *     position - index 对应的子组件相对于 selectedIndex 对应的子组件的起始位置的移动比例
       *       比如 0 1 2 当前显示的是 1,那么 0 的位置就是 -1,1 的位置就是 0,2 的位置就是 1
       *       比如 0 1 2 当前显示的是 1.5,那么 1 的位置就是 -0.5,2 的位置就是 0.5
       *     mainAxisLength - index 对应的子组件的主轴方向上的长度
       */
      Swiper(this.swiperController) {
        LazyForEach(this.array, (item: string) => {
          Text(item).backgroundColor(Color.Orange).fontColor(Color.White)
            .fontSize(24).textAlign(TextAlign.Center).height(100).width('100%')
        }, (item: string) => item)
      }
      .onChange((index: number) => {
        this.message1 = `onChange index:${index}`
      })
      .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
        this.message2 = `onGestureSwipe index:${index}, currentOffset:${extraInfo.currentOffset}`
      })
      .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
        this.message3 = `onAnimationStart index:${index}, targetIndex:${targetIndex}, currentOffset:${extraInfo.currentOffset}, targetOffset:${extraInfo.targetOffset}, velocity:${extraInfo.velocity}`
      })
      .onAnimationEnd((index: number, extraInfo: SwiperAnimationEvent) => {
        this.message4 = `onAnimationEnd index:${index}`
      })
      .onContentDidScroll((selectedIndex: number, index: number, position: number, mainAxisLength: number) => {
        MyLog.d(`onContentDidScroll index:${index}, selectedIndex:${selectedIndex}, position:${position}, mainAxisLength:${mainAxisLength}`)
      })
      .curve(Curve.Linear)
      .duration(5000).nextMargin(20)

      /*
       * SwiperController - 用于和绑定的 Swiper 之间的交互
       *   showNext() - 切换到下一页
       *   showPrevious() - 切换到上一页
       *   changeIndex() - 切换到指定页
       *   finishAnimation() - 立即停止切换动画
       */
      Button('showNext()').onClick(() => {
        this.swiperController.showNext()
      })
      Button('showPrevious()').onClick(() => {
        this.swiperController.showPrevious()
      })
      Button('changeIndex(3)').onClick(() => {
        this.swiperController.changeIndex(3)
      })
      Button('finishAnimation()').onClick(() => {
        this.swiperController.finishAnimation()
      })

      Text(this.message1)
      Text(this.message2)
      Text(this.message3)
      Text(this.message4)
    }
    .alignItems(HorizontalAlign.Start)
  }
}

@Component
struct MySample4 {

  private array: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'];
  private colorList: number[] = []

  private MIN_SCALE: number = 0.75

  @State opacityList: number[] = []
  @State scaleList: number[] = []
  @State translateList: number[] = []
  @State zIndexList: number[] = []

  aboutToAppear(): void {
    for (let i = 0; i < this.array.length; i++) {
      this.colorList.push(Math.floor(Math.random() * (0xffffff + 1)))
      this.opacityList.push(1.0)
      this.scaleList.push(1.0)
      this.translateList.push(0.0)
      this.zIndexList.push(0)
    }
  }

  build() {
    Column({ space: 10 }) {
      /*
       * Swiper - 组件轮播列表
       *   customContentTransition() - 自定义切换动画
       *     timeout - 经测试,不知道是干啥用的
       *     transition - 通过回调的 SwiperContentTransitionProxy 自定义切换动画
       *       selectedIndex - 启动动画的子组件的索引位置
       *       index - 可视区内的子组件的索引位置
       *       position - index 对应的子组件相对于 selectedIndex 对应的子组件的起始位置的移动比例
       *         比如 0 1 2 当前显示的是 1,那么 0 的位置就是 -1,1 的位置就是 0,2 的位置就是 1
       *         比如 0 1 2 当前显示的是 1.5,那么 1 的位置就是 -0.5,2 的位置就是 0.5
       *       mainAxisLength - index 对应的子组件的主轴方向上的长度
       *       finishTransition() - 经测试,不知道是干啥用的
       */
      Swiper() {
        ForEach(this.array, (item: string, index: number) => {
          Text(item).backgroundColor(this.colorList[index]).fontColor(Color.White)
            .fontSize(48).textAlign(TextAlign.Center).height(300).width('100%')
            .opacity(this.opacityList[index])
            .scale({ x: this.scaleList[index], y: this.scaleList[index] })
            .translate({ x: this.translateList[index] })
            .zIndex(this.zIndexList[index])
        })
      }
      .customContentTransition({
        transition: (proxy: SwiperContentTransitionProxy) => {
          MyLog.d(`index:${proxy.index}, selectedIndex:${proxy.selectedIndex}, position:${proxy.position}, mainAxisLength:${proxy.mainAxisLength}`)
          if (proxy.position <= 0) {
            // 可视区内的左侧的子组件
            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 时
            this.opacityList[proxy.index] = 1 - proxy.position
            this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position)
            this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
            this.zIndexList[proxy.index] = -1
          }
        }
      })
    }
    .alignItems(HorizontalAlign.Start)
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-02-06 08:49  webabcd  阅读(160)  评论(0)    收藏  举报