43.[HarmonyOS NEXT Row案例十一] 构建智能分页控件:Row组件实现页码与翻页按钮的完美结合 原创
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
![43.[HarmonyOS NEXT Row案例十一] 构建智能分页控件:Row组件实现页码与翻页按钮的完美结合-鸿蒙开发者社区 43.[HarmonyOS NEXT Row案例十一] 构建智能分页控件:Row组件实现页码与翻页按钮的完美结合-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202506/812b5944074444d86ef05029a341bad1dca812.jpg?x-oss-process=image/resize,w_534,h_556)
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组件创建功能完善的分页控件,实现页码显示与前后翻页按钮的完美结合。




















