项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
分页控件是数据展示类应用中不可或缺的导航元素,它允许用户在大量数据中进行有序浏览。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件创建一个功能完善的分页控件,实现页码显示与前后翻页按钮的完美结合。
分页控件在各类应用场景中广泛应用,如电子商城的商品列表、新闻应用的文章列表、图库应用的图片浏览等。通过合理的设计和交互,可以提升用户的浏览体验和数据访问效率。
2. 分页控件的设计原则
在设计分页控件时,需要遵循以下设计原则:
- 简洁明了:控件布局应简洁明了,用户能够一目了然地理解其功能。
- 状态清晰:当前页码和可用操作应有明确的视觉区分。
- 反馈及时:用户操作后应提供及时的视觉反馈。
- 边界处理:在首页和末页时,相应的翻页按钮应有适当的状态变化。
- 适应性强:控件应能适应不同的屏幕尺寸和方向。
3. 案例分析:分页控件
本案例展示了如何创建一个包含上一页按钮、页码显示和下一页按钮的分页控件。
3.1 完整代码
@Component export struct Pagination { @State currentPage: number = 1 @State totalPages: number = 10 build() { Row() { Button('上一页',{stateEffect: this.currentPage <= 1 ? false : true }) .opacity(this.currentPage <= 1 ? 0.4:1) .backgroundColor(0x317aff) .onClick(() => { if (this.currentPage > 1) this.currentPage-- }) Text(`第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`) .margin({ left: 12, right: 12 }) Button('下一页',{stateEffect:this.currentPage === this.totalPages ? false : true }) .opacity(this.currentPage === this.totalPages ? 0.4:1) .backgroundColor(0x317aff) .onClick(() => { if (this.currentPage < this.totalPages) this.currentPage++ }) } .height(40) .justifyContent(FlexAlign.Center) // 居中对齐 .margin(24) } }
3.2 代码详解
3.2.1 组件声明与状态定义
@Component export struct Pagination { @State currentPage: number = 1 @State totalPages: number = 10
这部分代码声明了一个名为Pagination的自定义组件,并定义了两个状态变量:
状态变量 |
类型 |
说明 |
默认值 |
currentPage |
number |
当前页码 |
1 |
totalPages |
number |
总页数 |
10 |
使用@State装饰器定义的状态变量会在值变化时自动触发UI更新,这对于实现分页控件的交互非常重要。
3.2.2 Row容器设置
Row() { // 子组件 } .height(40) .justifyContent(FlexAlign.Center) // 居中对齐 .margin(24)
这部分代码创建了一个Row容器,作为分页控件的根容器。Row容器的属性设置如下:
属性 |
值 |
说明 |
height |
40 |
设置容器高度为40vp,提供足够的点击区域 |
justifyContent |
FlexAlign.Center |
设置子组件在主轴(水平方向)上居中对齐 |
margin |
24 |
设置外边距为24vp,与其他元素保持适当距离 |
这些设置使分页控件具有合适的高度和间距,并且子组件在水平方向上居中排列,创造平衡的视觉效果。
3.2.3 上一页按钮设置
Button('上一页',{stateEffect: this.currentPage <= 1 ? false : true }) .opacity(this.currentPage <= 1 ? 0.4:1) .backgroundColor(0x317aff) .onClick(() => { if (this.currentPage > 1) this.currentPage-- })
这部分代码创建了一个"上一页"按钮,并设置了其样式和交互行为:
属性/方法 |
值/实现 |
说明 |
文本 |
'上一页' |
按钮显示的文本 |
stateEffect |
this.currentPage <= 1 ? false : true |
当前页为第一页时禁用按钮状态效果 |
opacity |
this.currentPage <= 1 ? 0.4 : 1 |
当前页为第一页时降低按钮透明度,视觉上表示不可用 |
backgroundColor |
0x317aff |
设置按钮背景色为蓝色 |
onClick |
回调函数 |
点击时将当前页码减1,但不小于1 |
这些设置使"上一页"按钮在当前页为第一页时呈现禁用状态(透明度降低且无状态效果),提示用户已经到达首页,无法继续向前翻页。
3.2.4 页码显示设置
Text(`第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`) .margin({ left: 12, right: 12 })
这部分代码创建了一个Text组件,用于显示当前页码和总页数:
属性 |
值 |
说明 |
文本 |
模板字符串 |
显示"第 X 页 / 共 Y 页"格式的文本,X为当前页码,Y为总页数 |
margin |
{ left: 12, right: 12 } |
设置左右外边距为12vp,与两侧按钮保持适当距离 |
页码显示采用模板字符串动态生成文本内容,当currentPage或totalPages状态变量变化时,文本内容会自动更新。
3.2.5 下一页按钮设置
Button('下一页',{stateEffect:this.currentPage === this.totalPages ? false : true }) .opacity(this.currentPage === this.totalPages ? 0.4:1) .backgroundColor(0x317aff) .onClick(() => { if (this.currentPage < this.totalPages) this.currentPage++ })
这部分代码创建了一个"下一页"按钮,并设置了其样式和交互行为:
属性/方法 |
值/实现 |
说明 |
文本 |
'下一页' |
按钮显示的文本 |
stateEffect |
this.currentPage === this.totalPages ? false : true |
当前页为最后一页时禁用按钮状态效果 |
opacity |
this.currentPage === this.totalPages ? 0.4 : 1 |
当前页为最后一页时降低按钮透明度,视觉上表示不可用 |
backgroundColor |
0x317aff |
设置按钮背景色为蓝色 |
onClick |
回调函数 |
点击时将当前页码加1,但不大于总页数 |
这些设置使"下一页"按钮在当前页为最后一页时呈现禁用状态(透明度降低且无状态效果),提示用户已经到达末页,无法继续向后翻页。
4. 分页控件的实现原理
分页控件的实现基于状态管理和条件渲染,通过状态变量控制UI的变化,实现交互效果。
4.1 状态管理
本案例使用两个状态变量管理分页控件的状态:
- currentPage:记录当前页码,初始值为1。
- totalPages:记录总页数,初始值为10。
这两个状态变量使用@State装饰器定义,确保它们的变化会自动触发UI更新。
4.2 条件渲染
通过条件表达式,根据当前状态动态调整UI元素的样式和行为:
- 上一页按钮:
- 当
currentPage <= 1时,按钮透明度降低且禁用状态效果,表示不可用。 - 点击时,只有当
currentPage > 1时才减少页码。
- 下一页按钮:
- 当
currentPage === totalPages时,按钮透明度降低且禁用状态效果,表示不可用。 - 点击时,只有当
currentPage < totalPages时才增加页码。
4.3 事件处理
通过onClick事件处理函数,响应用户的点击操作:
- 上一页按钮:点击时检查当前页码是否大于1,如果是则将
currentPage减1。 - 下一页按钮:点击时检查当前页码是否小于总页数,如果是则将
currentPage加1。
这种实现方式确保了分页控件的正确行为,防止页码超出有效范围。
5. 分页控件的样式优化
为了提升分页控件的视觉效果和用户体验,我们可以进行以下样式优化:
5.1 按钮样式优化
Button('上一页', { stateEffect: this.currentPage <= 1 ? false : true }) .opacity(this.currentPage <= 1 ? 0.4 : 1) .backgroundColor(0x317aff) .fontColor(Color.White) .fontSize(14) .height(36) .borderRadius(18) .padding({ left: 16, right: 16 }) .onClick(() => { if (this.currentPage > 1) this.currentPage-- })
这些样式设置使按钮更加美观:
- 字体颜色:设置为白色,与蓝色背景形成鲜明对比,提高可读性。
- 字体大小:设置为14fp,适合按钮文本的显示。
- 高度:设置为36vp,提供合适的点击区域。
- 圆角:设置为18vp(高度的一半),使按钮呈现为胶囊形状。
- 内边距:设置左右内边距,使文本与按钮边缘保持适当距离。
5.2 页码文本样式优化
Text(`第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`) .fontSize(14) .fontColor('#333333') .fontWeight(FontWeight.Medium) .margin({ left: 12, right: 12 })
这些样式设置使页码文本更加清晰:
- 字体大小:设置为14fp,与按钮文本保持一致。
- 字体颜色:设置为深灰色,提高可读性。
- 字体粗细:设置为中等粗细,使当前页码更加突出。
5.3 容器样式优化
Row() { // 子组件 } .height(40) .justifyContent(FlexAlign.Center) .backgroundColor('#F5F5F5') .borderRadius(20) .padding({ left: 8, right: 8 }) .margin(24)
这些样式设置使分页控件整体更加美观:
- 背景色:设置为浅灰色,使控件在页面中更加突出。
- 圆角:设置为20vp,使控件呈现为胶囊形状。
- 内边距:设置左右内边距,使子组件与容器边缘保持适当距离。
6. 分页控件的交互优化
为了提升用户体验,可以为分页控件添加更多交互效果:
6.1 按钮过渡动画
Button('上一页', { stateEffect: this.currentPage <= 1 ? false : true }) .opacity(this.currentPage <= 1 ? 0.4 : 1) .backgroundColor(0x317aff) // 其他样式... .transition({ type: TransitionType.All, opacity: 0.2 }) // 添加过渡动画 .onClick(() => { if (this.currentPage > 1) this.currentPage-- })
添加过渡动画使按钮状态变化更加平滑,提升视觉体验。
6.2 页码变化动画
@Component export struct Pagination { @State currentPage: number = 1 @State totalPages: number = 10 @State isAnimating: boolean = false build() { Row() { // 上一页按钮... Stack() { Text(`第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`) .fontSize(14) .fontColor('#333333') .fontWeight(FontWeight.Medium) .opacity(this.isAnimating ? 0 : 1) // 动画期间隐藏 .transition({ type: TransitionType.All, opacity: 0.2 }) } .width(120) .height(36) .alignContent(Alignment.Center) .margin({ left: 12, right: 12 }) // 下一页按钮... } // 容器样式... } private animatePageChange() { this.isAnimating = true setTimeout(() => { this.isAnimating = false }, 200) } private prevPage() { if (this.currentPage > 1) { this.animatePageChange() this.currentPage-- } } private nextPage() { if (this.currentPage < this.totalPages) { this.animatePageChange() this.currentPage++ } } }
这个优化版本添加了页码变化动画,当页码变化时,文本会先淡出再淡入,提供更好的视觉反馈。
7. 分页控件的扩展功能
基于本案例的基本结构,我们可以扩展更多功能:
7.1 页码跳转
@Component export struct PaginationWithJump { @State currentPage: number = 1 @State totalPages: number = 10 @State inputPage: string = '1' build() { Row() { // 上一页按钮... // 页码显示... // 下一页按钮... // 页码跳转 TextInput({ text: this.inputPage }) .width(50) .height(36) .backgroundColor(Color.White) .borderRadius(4) .margin({ left: 12 }) .type(InputType.Number) .onChange((value) => { this.inputPage = value }) Button('跳转') .height(36) .backgroundColor(0x317aff) .fontColor(Color.White) .fontSize(14) .borderRadius(4) .margin({ left: 8 }) .onClick(() => { const page = parseInt(this.inputPage) if (!isNaN(page) && page >= 1 && page <= this.totalPages) { this.currentPage = page } }) } // 容器样式... } }
这个扩展功能添加了页码跳转功能,用户可以输入页码并点击跳转按钮直接跳转到指定页面。
7.2 页码选择器
@Component export struct PaginationWithSelector { @State currentPage: number = 1 @State totalPages: number = 10 @State showSelector: boolean = false build() { Column() { Row() { // 上一页按钮... Text(`第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`) .fontSize(14) .fontColor('#333333') .fontWeight(FontWeight.Medium) .margin({ left: 12, right: 12 }) .onClick(() => { this.showSelector = !this.showSelector }) // 下一页按钮... } .height(40) .justifyContent(FlexAlign.Center) .margin(24) if (this.showSelector) { // 页码选择器 Grid() { ForEach(Array.from({ length: this.totalPages }, (_, i) => i + 1), (page) => { GridItem() { Text(`${page}`) .width('100%') .height('100%') .textAlign(TextAlign.Center) .backgroundColor(page === this.currentPage ? 0x317aff : Color.White) .fontColor(page === this.currentPage ? Color.White : '#333333') .borderRadius(4) .onClick(() => { this.currentPage = page this.showSelector = false }) } }) } .columnsTemplate('1fr 1fr 1fr 1fr 1fr') .rowsGap(8) .columnsGap(8) .padding(16) .backgroundColor(Color.White) .borderRadius(8) .shadow({ radius: 4, color: '#F0F0F0' }) .width('80%') .margin({ top: -16 }) } } } }
这个扩展功能添加了页码选择器,用户可以点击页码文本显示页码网格,然后直接选择要跳转的页码。
7.3 页码范围显示
@Component export struct PaginationWithRange { @State currentPage: number = 1 @State totalPages: number = 10 private maxVisiblePages: number = 5 build() { Row() { // 上一页按钮... // 页码范围 Row() { // 显示第一页 if (this.getStartPage() > 1) { Text('1') .fontSize(14) .fontColor('#333333') .width(36) .height(36) .textAlign(TextAlign.Center) .borderRadius(18) .backgroundColor(1 === this.currentPage ? 0x317aff : Color.Transparent) .fontColor(1 === this.currentPage ? Color.White : '#333333') .onClick(() => { this.currentPage = 1 }) // 显示省略号 if (this.getStartPage() > 2) { Text('...') .fontSize(14) .fontColor('#333333') .width(36) .height(36) .textAlign(TextAlign.Center) } } // 显示页码范围 ForEach(this.getPageRange(), (page) => { Text(`${page}`) .fontSize(14) .width(36) .height(36) .textAlign(TextAlign.Center) .borderRadius(18) .backgroundColor(page === this.currentPage ? 0x317aff : Color.Transparent) .fontColor(page === this.currentPage ? Color.White : '#333333') .onClick(() => { this.currentPage = page }) }) // 显示最后一页 if (this.getEndPage() < this.totalPages) { // 显示省略号 if (this.getEndPage() < this.totalPages - 1) { Text('...') .fontSize(14) .fontColor('#333333') .width(36) .height(36) .textAlign(TextAlign.Center) } Text(`${this.totalPages}`) .fontSize(14) .width(36) .height(36) .textAlign(TextAlign.Center) .borderRadius(18) .backgroundColor(this.totalPages === this.currentPage ? 0x317aff : Color.Transparent) .fontColor(this.totalPages === this.currentPage ? Color.White : '#333333') .onClick(() => { this.currentPage = this.totalPages }) } } .margin({ left: 8, right: 8 }) // 下一页按钮... } // 容器样式... } private getStartPage(): number { return Math.max(1, Math.min(this.currentPage - Math.floor(this.maxVisiblePages / 2), this.totalPages - this.maxVisiblePages + 1)) } private getEndPage(): number { return Math.min(this.totalPages, this.getStartPage() + this.maxVisiblePages - 1) } private getPageRange(): number[] { const start = this.getStartPage() const end = this.getEndPage() return Array.from({ length: end - start + 1 }, (_, i) => start + i) } }
这个扩展功能显示页码范围,而不是简单的当前页码和总页数,用户可以直接点击页码进行跳转。对于页码较多的情况,会显示省略号,保持界面简洁。
8. 分页控件组件的封装与复用
为了提高代码复用性,可以将分页控件封装为一个独立的组件:
@Component export struct Pagination { // 可自定义的属性 @Prop currentPage: number = 1 @Prop totalPages: number = 10 @Link pageIndex: number // 使用@Link装饰器,实现双向绑定 onPageChange?: (page: number) => void // 页码变化回调 buttonStyle?: ButtonStyle // 按钮样式 textStyle?: TextStyle // 文本样式 build() { Row() { Button('上一页', { stateEffect: this.pageIndex <= 1 ? false : true, type: this.buttonStyle?.type || ButtonType.Normal }) .opacity(this.pageIndex <= 1 ? 0.4 : 1) .backgroundColor(this.buttonStyle?.backgroundColor || 0x317aff) .fontColor(this.buttonStyle?.fontColor || Color.White) .fontSize(this.buttonStyle?.fontSize || 14) .height(this.buttonStyle?.height || 36) .borderRadius(this.buttonStyle?.borderRadius || 18) .padding({ left: 16, right: 16 }) .onClick(() => { if (this.pageIndex > 1) { this.pageIndex-- if (this.onPageChange) { this.onPageChange(this.pageIndex) } } }) Text(`第 ${this.pageIndex} 页 / 共 ${this.totalPages} 页`) .fontSize(this.textStyle?.fontSize || 14) .fontColor(this.textStyle?.fontColor || '#333333') .fontWeight(this.textStyle?.fontWeight || FontWeight.Medium) .margin({ left: 12, right: 12 }) Button('下一页', { stateEffect: this.pageIndex === this.totalPages ? false : true, type: this.buttonStyle?.type || ButtonType.Normal }) .opacity(this.pageIndex === this.totalPages ? 0.4 : 1) .backgroundColor(this.buttonStyle?.backgroundColor || 0x317aff) .fontColor(this.buttonStyle?.fontColor || Color.White) .fontSize(this.buttonStyle?.fontSize || 14) .height(this.buttonStyle?.height || 36) .borderRadius(this.buttonStyle?.borderRadius || 18) .padding({ left: 16, right: 16 }) .onClick(() => { if (this.pageIndex < this.totalPages) { this.pageIndex++ if (this.onPageChange) { this.onPageChange(this.pageIndex) } } }) } .height(40) .justifyContent(FlexAlign.Center) .margin(24) } } // 按钮样式接口 interface ButtonStyle { type?: ButtonType backgroundColor?: Color | number | string fontColor?: Color | number | string fontSize?: number height?: number borderRadius?: number } // 文本样式接口 interface TextStyle { fontSize?: number fontColor?: Color | number | string fontWeight?: FontWeight }
然后在应用中使用这个组件:
@Entry @Component struct PaginationDemo { @State currentPage: number = 1 private totalPages: number = 10 build() { Column({ space: 20 }) { // 默认样式 Pagination({ pageIndex: this.$currentPage, totalPages: this.totalPages, onPageChange: (page) => { console.info(`页码变化:${page}`) } }) // 自定义样式 Pagination({ pageIndex: this.$currentPage, totalPages: this.totalPages, buttonStyle: { backgroundColor: 0xFF5722, borderRadius: 4 }, textStyle: { fontSize: 16, fontColor: '#666666' } }) // 显示当前数据 Text(`当前显示:第 ${this.currentPage} 页数据`) .fontSize(16) .fontWeight(FontWeight.Medium) .margin({ top: 20 }) } .width('100%') .height('100%') .backgroundColor('#F5F5F5') .padding({ top: 20 }) } }
通过这种方式,可以创建具有不同样式和行为的分页控件,提高代码复用性和开发效率。
9. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的Row组件创建功能完善的分页控件,实现页码显示与前后翻页按钮的完美结合。