[应用开发]HarmonyOS鸿蒙Next中Swiper组件设计开发

[应用开发]HarmonyOS鸿蒙Next中Swiper组件设计开发 随易App开发实战 :Swiper组件响应式布局

项目地址:app_EasyRandom: 随易app (gitee.com)

本案例,将通过Swiper组件ForEach循环渲染配合断点功能,设计实现一个完整的响应式的Swiper页面案例。此轮播图组件会根据设备宽度自动更改轮播图的显示数量。

相关技术要素:

  • ForEach:循环渲染
  • Swiper:轮播图组件
  • 断点监听:实现响应式布局

本案例依照本人开发的原生应用随易,本案例完整代码将写在文章最后,项目的完整代码和详情请访问:随易Gitee项目地址(本案例文件位于项目product/default/src/main/ets/pages/RandomPage文件中)

项目整体结构:

├──common/src/main/ets                       /公共库                      
  ├──components                             //公共组件
  └──utils                                  //公共函数库
├──product/default/src/main/ets              // 代码区                      
  ├──entryability                           //页面Ability
  ├──components                             //子组件
  ├──constants                              //公共常量
  ├──datas                                  //页面数据
  ├──pages                                  //主要页面
  ├──defaultformability                     //卡片Ability
  └──widgets                                //卡片组件
└──product/default/src/main/resources        // 资源文件目录

具体实现:

  1. 首先要进行对于组件的数据类型进行分析,通过统一的数据类型方便后续的循环渲染,轮播图组件的核心是图片和标题,在功能性上则需要根据需求设计(例如有跳转功能可以加入跳转的链接与参数)。为了美观性我们还可以定义一个颜色,因此设计如下的数据结构:
class AppItem {
  name: string  //标题
  img: Resource //图片
  url: string | null    //链接
  params: string | null //参数
  colors: Array<[ResourceColor, number]> | null  //颜色

  constructor(name: string, img: Resource, url: string, params: string | null,
    colors: Array<[ResourceColor, number]> | null) {
    this.name = name;
    this.img = img;
    this.url = url
    this.params = params
    this.colors = colors
  }
}
  1. 实用轮播图Swiper组件配合列表渲染完成组件的搭建

2.1 轮播图子组件封装

@Builder
swiperItem(app: AppItem) {
  Image(app.img)
    .width('100%')
    .aspectRatio(1)
    .objectFit(ImageFit.Fill)
    .borderRadius(16)
    .backgroundColor(0x00beff)
    .onClick(() => {
      router.pushUrl({ url: app.url, params: app.params })
    })
}

通过子组件的封装和统一的数据结构,方便后续的列表渲染。在组件添加onClick函数,统一组件的功能性。如此处为组件添加router功能,在数据定义时便可统一给组件的跳转位置和附带参数进行赋值,方便后续的修改与添加新组件。

2.2 forEach循环渲染

let Swipers: Array<AppItem> = [
  new AppItem('真心话大冒险', $r("app.media.icon_HonestOrChallenge"),
    'components/HonestOrChallenge', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),
  new AppItem('幸运转盘', $r("app.media.icon_Rolls"),
    'pages/Index', '', [[0x00BFFF, 0], [0x1E90FF, 0.5], [0x00008B, 1]]),
  new AppItem('人生必去的100个地方', $r("app.media.icon_RandomPlaces"),
    'components/RandomPlaces', '', [[0x00beff, 0], [0xffeb00, 0.5], [0xffff00, 1]]),
]//测试数据

Swiper(
        ForEach(Swipers, (app: AppItem) => {
            this.swiperItem(app)        
        })
)

通过forEach循环渲染,我们可以方便的通过数据驱动来修改页面内容,方便后续的更改与维护。

2.3 调整组件样式

外观样式比较主观,仅作简单参考(完整示例代码在文章末尾)。Swiper组件的具体开发细节可查看官方文档,在此列举几个重要的属性和用法:

  • autoPlay(false):是否自动轮播
  • indicator(true):是否显示下标
  • itemSpace(10):子组件间距
  • displayCount():显示数量,此属性为后续的响应式布局提供基础,内部传入number类型可决定子组件的显示数量

响应式(通过监听断点实现)

首先要了解ArkUI有很多种实现响应式和监听的方式:可在此链接选择响应式布局-布局能力,这里选择使用断点BreakPoint来实现,断点的基础使用方式如下:

import { BreakpointState, BreakpointSystem } from '@ohos/common'
//以上文件为从官网案例中下载

@State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })
aboutToAppear() {
  BreakpointSystem.getInstance().attach(this.currentBreakpoint)
  BreakpointSystem.getInstance().start()
}
aboutToDisappear() { 
  BreakpointSystem.getInstance().attach(this.currentBreakpoint)
  BreakpointSystem.getInstance().stop()
}

此响应式的关键点便在于将断点值与上文提到的重要属性displayCount有机结合,使用三元运算符this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3)便可实现小设备显示1个,中设备显示两个,大设备显示三个的效果。可根据实际情况进行调整,使用其它监听方式也同样可以使用此方法完成响应式操作,完整写法如下:

@Component
struct Banner {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }

  @Builder
  swiperItem(app: AppItem) {
    Image(app.img)
      .width('100%')
      .aspectRatio(1)
      .objectFit(ImageFit.Fill)
      .borderRadius(16)
      .backgroundColor(0x00beff)
      .onClick(() => {
        router.pushUrl({ url: app.url, params: app.params })
      })
  }

  build() {
    Swiper() {
        ForEach(Swipers, (app: AppItem) => {
            this.swiperItem(app)        
        })
    }
    .autoPlay(false)
    .indicator(true)
    .itemSpace(10)
    .displayCount(this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3))
    .width('100%')
    .padding({
      bottom: 36,
    })
  }
}

项目完整代码可前往app_EasyRandom - 码云 - 开源中国 (gitee.com)

页面完整代码:

import { BreakpointState, BreakpointSystem } from '@ohos/common'
import { router } from '@kit.ArkUI'

@Entry
@Component
struct RandomPage {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }
  build() {
    List() {
      ListItem().margin({top:12})
      ListItem() {
        Banner()
      }

      ListItem() {
        AppList()
      }
    }
    .width("100%")
    .padding({
      left:this.currentBreakpoint.value=='sm'?12:24,
      right:this.currentBreakpoint.value=='sm'?12:24
    })
  }
}

export default RandomPage


class AppItem {
  name: string
  img: Resource
  url: string | null
  params: string | null
  colors: Array<[ResourceColor, number]> | null

  constructor(name: string, img: Resource, url: string, params: string | null,
    colors: Array<[ResourceColor, number]> | null) {
    this.name = name;
    this.img = img;
    this.url = url
    this.params = params
    this.colors = colors
  }
}

let Swipers: Array<AppItem> = [
  new AppItem('真心话大冒险', $r("app.media.icon_HonestOrChallenge"),
    'components/HonestOrChallenge', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),

  new AppItem('幸运转盘', $r("app.media.icon_Rolls"),
    'pages/Index', '', [[0x00BFFF, 0], [0x1E90FF, 0.5], [0x00008B, 1]]),

  new AppItem('人生必去的100个地方', $r("app.media.icon_RandomPlaces"),
    'components/RandomPlaces', '', [[0x00beff, 0], [0xffeb00, 0.5], [0xffff00, 1]]),
]

@Component
struct Banner {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }

  @Builder
  swiperItem(app: AppItem) {
    Image(app.img)
      .width('100%')
      .aspectRatio(1)
      .objectFit(ImageFit.Fill)
      .borderRadius(16)
      .backgroundColor(0x00beff)
      .onClick(() => {
        router.pushUrl({ url: app.url, params: app.params })
      })
  }

  build() {
    Swiper() {
      this.swiperItem(Swipers[0])
      this.swiperItem(Swipers[1])
      this.swiperItem(Swipers[2])
    }
    .autoPlay(false)
    .indicator(true)
    .itemSpace(10)
    .displayCount(this.currentBreakpoint.value === 'sm' ? 1 : (this.currentBreakpoint.value === 'md' ? 2 : 3))
    .width('100%')
    .padding({
     // left: 12,
      //right: 12,
      bottom: 36,
      //top: 16
    })
  }
}

let Apps: Array<AppItem> = [
  new AppItem('颜色搭配', $r("app.media.icon_RandomColors"),
    'components/RandomColors', '', [[0xDDA0DD, 0], [0xEE82EE, 0.5], [0x9400D3, 1]]),

  new AppItem('扔硬币', $r("app.media.icon_FlipCoin"),
    'components/FlipCoin', '', [[0xFF6347, 0], [0xFFD700, 0.5], [0xFFA500, 1]]),

  new AppItem('抽签', $r("app.media.icon_RandomNames"),
    'components/RandomNames', '', [[0x98FB98, 0], [0x32CD32, 0.5], [0x006400, 1]]),

  new AppItem('掷骰子', $r("app.media.icon_RollDices"),
    'components/RollDices', '', [[0x6A5ACD, 0], [0x483D8B, 0.5], [0x0000FF, 1]]),

  new AppItem('吃什么', $r("app.media.icon_RandomFoods"),
    'components/RandomFoods', '', [[0xADFF2F, 0], [0x7FFF00, 0.5], [0x008000, 1]]),

  new AppItem('随机数', $r("app.media.icon_RollNumbers"),
    'components/RandomNumbers', '', [[0xFFDAB9, 0], [0xFFE4B5, 0.5], [0xB8860B, 1]]),

]

@Component
struct AppList {
  @State currentBreakpoint: BreakpointState<string> = BreakpointState.of({ sm: "sm", md: "md", lg: "lg" })
  private apps: Array<AppItem> = Apps;

  aboutToAppear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().start()
  }

  aboutToDisappear() {
    BreakpointSystem.getInstance().attach(this.currentBreakpoint)
    BreakpointSystem.getInstance().stop()
  }

  @Builder
  appListItem(app: AppItem) {
    Column() {

      Image(app.img)
        .width(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .height(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .margin({ bottom: 8 })//.backgroundColor(app.bgc)
        .linearGradient({ angle: 180, colors: app.colors })
        .clip(true)
        .aspectRatio(1)
        .borderRadius(16)
      Text(app.name)
        .width(this.currentBreakpoint.value === 'lg' ? 120 : 112)//80 56
        .height(32)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Center)
        .fontColor('#18181A')
        .margin({ bottom: 8 })
    }
    .onClick(() => {
      router.pushUrl({ url: app.url, params: app.params })
    })
  }

  build() {
    Column() {
      Row() {
        Text('热门功能').fontSize(20).fontWeight(FontWeight.Bold)
      }.justifyContent(FlexAlign.Start).width('100%').height(40).alignItems(VerticalAlign.Center)

      List({ space: this.currentBreakpoint.value === 'lg' ? 22 : 20 }) {
        ForEach(this.apps, (app: AppItem) => {
          ListItem() {
            this.appListItem(app)
          }
        })
      }
      .width('100%')
      .height(this.currentBreakpoint.value === 'lg' ? 320 : 280)
      .listDirection(Axis.Horizontal)
    }
    .width('100%')
    .height(this.currentBreakpoint.value === 'lg' ? 338 : 218)
    .padding({
      bottom: 8,
      //left: 12,
    //  right: 12
      })
  }
}
2 回复

在HarmonyOS鸿蒙Next中,Swiper组件用于实现滑动切换内容的效果,类似于轮播图。Swiper组件支持水平或垂直方向的滑动,并且可以设置自动播放、循环播放等特性。开发时,可以通过SwiperSwiperController类来实现滑动逻辑的控制。

在布局文件中,使用<Swiper>标签定义Swiper组件,并通过<SwiperItem>标签添加子项。每个SwiperItem可以包含任意UI组件,如TextImage等。通过设置Swiperindex属性,可以指定初始显示的项。

在代码中,可以通过SwiperController来控制Swiper的行为,例如跳转到指定项、开始或停止自动播放等。Swiper组件还支持手势操作,用户可以通过滑动屏幕来切换内容。

示例代码:

@Entry
@Component
struct SwiperExample {
  private swiperController: SwiperController = new SwiperController()

  build() {
    Swiper(this.swiperController) {
      SwiperItem() {
        Text('Item 1')
      }
      SwiperItem() {
        Text('Item 2')
      }
      SwiperItem() {
        Text('Item 3')
      }
    }
    .autoPlay(true)
    .loop(true)
    .onChange((index: number) => {
      console.log(`Current index: ${index}`)
    })
  }
}

Swiper组件还支持自定义指示器、设置滑动动画等高级功能,开发者可以根据需求进行配置。

更多关于[应用开发]HarmonyOS鸿蒙Next中Swiper组件设计开发的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,Swiper组件用于实现轮播图效果,支持水平或垂直滑动。开发时,首先在XML布局文件中定义Swiper组件,设置其方向、自动播放等属性。然后,在Java或JS代码中绑定数据源,通过Adapter动态加载内容。可通过监听滑动事件实现自定义交互,如点击跳转、指示器更新等。Swiper组件支持手势操作,确保用户体验流畅。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!