[应用开发]HarmonyOS鸿蒙Next中Swiper组件设计开发
[应用开发]HarmonyOS鸿蒙Next中Swiper组件设计开发 随易App开发实战 :Swiper组件响应式布局
项目地址:app_EasyRandom: 随易app (gitee.com)
本案例,将通过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 // 资源文件目录
具体实现:
- 首先要进行对于组件的数据类型进行分析,通过统一的数据类型方便后续的循环渲染,轮播图组件的核心是图片和标题,在功能性上则需要根据需求设计(例如有跳转功能可以加入跳转的链接与参数)。为了美观性我们还可以定义一个颜色,因此设计如下的数据结构:
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
}
}
- 实用轮播图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
})
}
}
在HarmonyOS鸿蒙Next中,Swiper组件用于实现滑动切换内容的效果,类似于轮播图。Swiper组件支持水平或垂直方向的滑动,并且可以设置自动播放、循环播放等特性。开发时,可以通过Swiper
和SwiperController
类来实现滑动逻辑的控制。
在布局文件中,使用<Swiper>
标签定义Swiper组件,并通过<SwiperItem>
标签添加子项。每个SwiperItem
可以包含任意UI组件,如Text
、Image
等。通过设置Swiper
的index
属性,可以指定初始显示的项。
在代码中,可以通过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组件支持手势操作,确保用户体验流畅。