项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
在移动应用界面设计中,水平滚动的标签列表是一种常见且实用的UI元素,它可以在有限的屏幕空间内展示多个分类或选项。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件结合Scroll容器创建一个水平滚动的标签列表,帮助开发者构建出流畅、美观的分类导航界面。
2. 水平滚动标签列表的设计原则
在设计水平滚动标签列表时,需要考虑以下几个关键原则:
- 可见性:确保用户能够意识到内容可以水平滚动,通常通过部分显示最右侧的标签来提示。
- 可访问性:标签大小应适中,便于用户点击,同时标签之间的间距应适当,避免误触。
- 视觉一致性:所有标签的样式应保持一致,包括字体、大小、颜色和形状。
- 滚动流畅性:滚动应流畅无阻,提供良好的用户体验。
3. 案例分析:水平滚动的标签列表
本案例展示了如何创建一个水平滚动的标签列表,通过Row组件排列多个标签,并使用Scroll容器实现水平滚动功能。
3.1 完整代码
@Component
export struct ScrollableTagList {
private tags: string[] = ['热门', '科技', '教育', '娱乐', '体育', '财经', '生活']
build() {
Scroll() {
Row({ space: 12 })
{
ForEach(this.tags, (tag:string) => {
Text(tag)
.padding({ left: 16, right:16, top: 6, bottom: 6 })
.backgroundColor(0xF0F8FF)
.borderRadius(20)
.fontSize(14)
}, (tag:string) => tag)
} .width('100%')
.height(40)
.padding({ left: 24 })
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
}
}
3.2 代码详解
3.2.1 组件声明与数据定义
@Component
export struct ScrollableTagList {
private tags: string[] = ['热门', '科技', '教育', '娱乐', '体育', '财经', '生活']
这部分代码声明了一个名为ScrollableTagList
的自定义组件,并定义了一个私有数组tags
,包含七个标签文本。在实际应用中,这些标签可能来自后端API或本地配置。
3.2.2 Scroll容器设置
Scroll() {
// 内容
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
这部分代码创建了一个Scroll容器,用于实现滚动功能。Scroll容器的属性设置如下:
属性 | 值 | 说明 |
---|---|---|
scrollable | ScrollDirection.Horizontal | 设置滚动方向为水平方向 |
backgroundColor | 0xFFFFFF | 设置背景色为白色 |
scrollable
属性是Scroll容器的核心属性,它指定了滚动的方向。在HarmonyOS NEXT中,ScrollDirection
枚举提供了两个值:
值 | 说明 |
---|---|
Horizontal | 水平滚动 |
Vertical | 垂直滚动 |
在本案例中,我们使用ScrollDirection.Horizontal
实现水平滚动。
3.2.3 Row容器设置
Row({ space: 12 })
{
// 子组件
} .width('100%')
.height(40)
.padding({ left: 24 })
这部分代码创建了一个Row容器,用于水平排列标签。Row容器的属性设置如下:
属性 | 值 | 说明 |
---|---|---|
space | 12 | 子组件之间的间距为12vp |
width | '100%' | 容器宽度为父容器的100% |
height | 40 | 容器高度为40vp |
padding | { left: 24 } | 左侧内边距为24vp |
这些设置确保了标签列表有足够的高度和适当的间距,左侧内边距使标签不会贴近屏幕边缘,提供更好的视觉效果。
3.2.4 标签循环渲染
ForEach(this.tags, (tag:string) => {
Text(tag)
.padding({ left: 16, right:16, top: 6, bottom: 6 })
.backgroundColor(0xF0F8FF)
.borderRadius(20)
.fontSize(14)
}, (tag:string) => tag)
这部分代码使用ForEach
组件循环渲染标签数组中的每一个标签。ForEach
接收三个参数:
- 要循环的数组:
this.tags
- 渲染函数:
(tag:string) => { ... }
,接收当前项作为参数 - 唯一键函数:
(tag:string) => tag
,用于标识每一项,优化重渲染性能
在渲染函数中,我们为每个标签创建一个Text组件。
3.2.5 标签样式设置
Text(tag)
.padding({ left: 16, right:16, top: 6, bottom: 6 })
.backgroundColor(0xF0F8FF)
.borderRadius(20)
.fontSize(14)
这部分代码创建了一个Text组件,显示标签文本。Text组件的属性设置如下:
属性 | 值 | 说明 |
---|---|---|
padding | { left: 16, right: 16, top: 6, bottom: 6 } | 设置内边距,使文本不会贴近标签边缘 |
backgroundColor | 0xF0F8FF | 设置背景色为浅蓝色 |
borderRadius | 20 | 设置边框圆角为20vp,使标签呈现为胶囊形状 |
fontSize | 14 | 设置字体大小为14fp |
这些设置使标签具有美观的外观,胶囊形状的设计是标签列表中的常见样式,能够清晰地区分不同的标签。
4. 水平滚动的实现原理
在HarmonyOS NEXT中,水平滚动主要通过Scroll容器实现。Scroll容器是一个专门用于处理滚动的容器组件,它可以在内容超出容器尺寸时提供滚动功能。
4.1 Scroll容器的工作原理
当Scroll容器的内容宽度超过容器宽度时,Scroll容器会自动提供水平滚动功能。在本案例中,Row容器中的多个标签可能超出屏幕宽度,此时Scroll容器会启用水平滚动,使用户可以通过滑动查看所有标签。
4.2 滚动方向的设置
通过scrollable
属性,我们可以设置Scroll容器的滚动方向:
.scrollable(ScrollDirection.Horizontal) // 水平滚动
如果需要垂直滚动,可以设置为:
.scrollable(ScrollDirection.Vertical) // 垂直滚动
4.3 滚动指示器
在某些场景下,可能需要显示滚动指示器,以提示用户内容可以滚动。可以通过scrollBar
属性设置滚动指示器的显示方式:
.scrollBar(BarState.Auto) // 自动显示滚动指示器
BarState
枚举提供了三个值:
值 | 说明 |
---|---|
Auto | 自动显示滚动指示器,滚动时显示,停止滚动后隐藏 |
On | 始终显示滚动指示器 |
Off | 始终隐藏滚动指示器 |
在标签列表这种场景中,通常不需要显示滚动指示器,因为标签列表的滚动是很直观的,用户可以通过部分显示的标签意识到可以滚动。
5. 标签列表的样式优化
为了提升标签列表的视觉效果和用户体验,我们可以进行以下优化:
5.1 标签间距优化
适当的标签间距可以提升视觉效果和点击精确度:
Row({ space: 12 }) // 标签间距为12vp
标签间距不宜过小,否则会使标签看起来拥挤,也不宜过大,否则会浪费屏幕空间。12vp是一个比较合适的值,既能区分不同标签,又不会浪费太多空间。
5.2 标签内边距优化
标签的内边距影响标签的大小和文本的显示效果:
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
水平内边距(左右)通常大于垂直内边距(上下),这样可以使标签看起来更宽松,文本有足够的呼吸空间。
5.3 标签圆角优化
标签的圆角大小影响标签的形状:
.borderRadius(20)
对于高度较小的标签(如本案例中高度约为26vp = 14fp + 6vp * 2),设置圆角为20vp可以使标签呈现为完美的胶囊形状。一般来说,圆角大小设置为标签高度的一半可以得到胶囊形状。
5.4 左侧内边距优化
Row容器的左侧内边距确保第一个标签不会贴近屏幕边缘:
.padding({ left: 24 })
这个内边距使标签列表看起来更加整洁,也符合大多数设计规范中对内容边距的要求。
6. 标签列表的交互优化
为了提升标签列表的交互体验,我们可以添加以下功能:
6.1 标签选中状态
通常,标签列表需要支持选中状态,以指示当前激活的分类:
@Component
export struct ScrollableTagList {
private tags: string[] = ['热门', '科技', '教育', '娱乐', '体育', '财经', '生活']
@State selectedIndex: number = 0 // 当前选中的标签索引
build() {
Scroll() {
Row({ space: 12 }) {
ForEach(this.tags, (tag:string, index) => {
Text(tag)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.backgroundColor(index === this.selectedIndex ? 0x007DFF : 0xF0F8FF)
.fontColor(index === this.selectedIndex ? 0xFFFFFF : 0x000000)
.borderRadius(20)
.fontSize(14)
.onClick(() => {
this.selectedIndex = index
})
}, (tag:string) => tag)
}
.width('100%')
.height(40)
.padding({ left: 24 })
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
}
}
在这个优化版本中,我们添加了一个selectedIndex
状态变量,用于跟踪当前选中的标签索引。然后,根据标签是否被选中,设置不同的背景色和文字颜色,并添加onClick
事件处理器,使用户可以通过点击切换选中的标签。
6.2 滚动到选中标签
当标签列表很长,选中的标签可能不在可视区域内时,我们可以添加自动滚动功能,确保选中的标签始终可见:
@Component
export struct ScrollableTagList {
private tags: string[] = ['热门', '科技', '教育', '娱乐', '体育', '财经', '生活']
@State selectedIndex: number = 0
private scrollController: ScrollController = new ScrollController()
build() {
Scroll(this.scrollController) {
Row({ space: 12 }) {
ForEach(this.tags, (tag:string, index) => {
Text(tag)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.backgroundColor(index === this.selectedIndex ? 0x007DFF : 0xF0F8FF)
.fontColor(index === this.selectedIndex ? 0xFFFFFF : 0x000000)
.borderRadius(20)
.fontSize(14)
.id('tag' + index) // 为每个标签设置ID
.onClick(() => {
this.selectedIndex = index
// 滚动到选中的标签
this.scrollController.scrollToElement('tag' + index, { behavior: ScrollBehavior.Smooth })
})
}, (tag:string) => tag)
}
.width('100%')
.height(40)
.padding({ left: 24 })
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
}
}
在这个版本中,我们添加了一个ScrollController
实例,用于控制Scroll容器的滚动行为。然后,为每个标签设置一个唯一的ID,并在标签被点击时,使用scrollToElement
方法滚动到选中的标签。
7. 标签列表的扩展功能
基于本案例的基本结构,我们可以扩展更多功能:
7.1 标签徽章
在某些场景下,可能需要在标签上显示徽章,如未读消息数量:
@Component
struct TagWithBadge {
tag: string
badgeCount?: number
isSelected: boolean
onClick?: () => void
build() {
Stack() {
Text(this.tag)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.backgroundColor(this.isSelected ? 0x007DFF : 0xF0F8FF)
.fontColor(this.isSelected ? 0xFFFFFF : 0x000000)
.borderRadius(20)
.fontSize(14)
if (this.badgeCount && this.badgeCount > 0) {
Text(this.badgeCount.toString())
.width(16)
.height(16)
.fontSize(10)
.fontColor(0xFFFFFF)
.backgroundColor(0xFF0000)
.borderRadius(8)
.textAlign(TextAlign.Center)
.position({ x: '100%', y: 0 })
.translate({ x: -8, y: -4 })
}
}
.onClick(() => {
if (this.onClick) {
this.onClick()
}
})
}
}
然后在标签列表中使用这个组件:
ForEach(this.tags, (tag:string, index) => {
TagWithBadge({
tag: tag,
badgeCount: index === 1 ? 5 : (index === 3 ? 2 : 0), // 示例:为某些标签添加徽章
isSelected: index === this.selectedIndex,
onClick: () => {
this.selectedIndex = index
this.scrollController.scrollToElement('tag' + index, { behavior: ScrollBehavior.Smooth })
}
})
}, (tag:string) => tag)
7.2 标签分组
在某些复杂场景下,可能需要将标签分组显示:
@Component
export struct GroupedTagList {
private tagGroups: Array<{
groupName: string,
tags: string[]
}> = [
{
groupName: '热门分类',
tags: ['热门', '推荐', '最新']
},
{
groupName: '内容分类',
tags: ['科技', '教育', '娱乐', '体育', '财经', '生活']
}
]
@State selectedGroup: number = 0
@State selectedTags: number[] = [0, 0] // 每个组中选中的标签索引
build() {
Column({ space: 16 }) {
// 组选择器
Row({ space: 24 }) {
ForEach(this.tagGroups, (group, groupIndex) => {
Text(group.groupName)
.fontSize(16)
.fontWeight(groupIndex === this.selectedGroup ? FontWeight.Bold : FontWeight.Normal)
.fontColor(groupIndex === this.selectedGroup ? 0x007DFF : 0x000000)
.onClick(() => {
this.selectedGroup = groupIndex
})
}, (group, index) => index.toString())
}
.width('100%')
.padding({ left: 24, right: 24 })
// 当前组的标签列表
Scroll() {
Row({ space: 12 }) {
ForEach(this.tagGroups[this.selectedGroup].tags, (tag, tagIndex) => {
Text(tag)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.backgroundColor(tagIndex === this.selectedTags[this.selectedGroup] ? 0x007DFF : 0xF0F8FF)
.fontColor(tagIndex === this.selectedTags[this.selectedGroup] ? 0xFFFFFF : 0x000000)
.borderRadius(20)
.fontSize(14)
.onClick(() => {
this.selectedTags[this.selectedGroup] = tagIndex
})
}, (tag, index) => index.toString())
}
.width('100%')
.height(40)
.padding({ left: 24 })
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
}
}
}
8. 标签列表组件的封装与复用
为了提高代码复用性,可以将标签列表封装为一个独立的组件:
@Component
export struct ScrollableTagList {
tags: string[] = []
@Link selectedIndex: number
onTagClick?: (index: number) => void
private scrollController: ScrollController = new ScrollController()
build() {
Scroll(this.scrollController) {
Row({ space: 12 }) {
ForEach(this.tags, (tag:string, index) => {
Text(tag)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.backgroundColor(index === this.selectedIndex ? 0x007DFF : 0xF0F8FF)
.fontColor(index === this.selectedIndex ? 0xFFFFFF : 0x000000)
.borderRadius(20)
.fontSize(14)
.id('tag' + index)
.onClick(() => {
this.selectedIndex = index
this.scrollController.scrollToElement('tag' + index, { behavior: ScrollBehavior.Smooth })
if (this.onTagClick) {
this.onTagClick(index)
}
})
}, (tag:string) => tag)
}
.width('100%')
.height(40)
.padding({ left: 24 })
}
.scrollable(ScrollDirection.Horizontal)
.backgroundColor(0xFFFFFF)
}
}
然后在应用中使用这个组件:
@Entry
@Component
struct MyPage {
@State categories: string[] = ['热门', '科技', '教育', '娱乐', '体育', '财经', '生活']
@State selectedCategory: number = 0
build() {
Column({ space: 16 }) {
// 标签列表
ScrollableTagList({
tags: this.categories,
selectedIndex: $selectedCategory,
onTagClick: (index) => {
// 处理标签点击事件
console.info('选中分类:' + this.categories[index])
}
})
// 内容区域
Text('当前选中:' + this.categories[this.selectedCategory])
.fontSize(16)
.padding(16)
}
.width('100%')
}
}
9. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的Row组件结合Scroll容器创建水平滚动的标签列表。通过本案例,我们学习了:
- 水平滚动标签列表的设计原则
- Scroll容器的基本用法和参数设置
- Row组件在标签列表中的应用
- 水平滚动的实现原理
- 标签列表的样式优化技巧
- 标签列表的交互优化方法
- 标签列表的扩展功能
- 标签列表组件的封装与复用
掌握这些知识点后,你可以设计出流畅、美观、易用的水平滚动标签列表,提升应用的用户体验。在实际开发中,可以根据具体需求调整标签样式、布局和功能,创建符合应用设计风格的标签导航界面。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。