项目已开源,开源地址: 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组件创建功能完善的分页控件,实现页码显示与前后翻页按钮的完美结合。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。