项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

1. 概述

在移动应用界面设计中,水平滚动的标签列表是一种常见且实用的UI元素,它可以在有限的屏幕空间内展示多个分类或选项。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件结合Scroll容器创建一个水平滚动的标签列表,帮助开发者构建出流畅、美观的分类导航界面。

2. 水平滚动标签列表的设计原则

在设计水平滚动标签列表时,需要考虑以下几个关键原则:

  1. 可见性:确保用户能够意识到内容可以水平滚动,通常通过部分显示最右侧的标签来提示。
  2. 可访问性:标签大小应适中,便于用户点击,同时标签之间的间距应适当,避免误触。
  3. 视觉一致性:所有标签的样式应保持一致,包括字体、大小、颜色和形状。
  4. 滚动流畅性:滚动应流畅无阻,提供良好的用户体验。

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容器的属性设置如下:

属性说明
scrollableScrollDirection.Horizontal设置滚动方向为水平方向
backgroundColor0xFFFFFF设置背景色为白色

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容器的属性设置如下:

属性说明
space12子组件之间的间距为12vp
width'100%'容器宽度为父容器的100%
height40容器高度为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接收三个参数:

  1. 要循环的数组:this.tags
  2. 渲染函数:(tag:string) => { ... },接收当前项作为参数
  3. 唯一键函数:(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 }设置内边距,使文本不会贴近标签边缘
backgroundColor0xF0F8FF设置背景色为浅蓝色
borderRadius20设置边框圆角为20vp,使标签呈现为胶囊形状
fontSize14设置字体大小为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容器创建水平滚动的标签列表。通过本案例,我们学习了:

  1. 水平滚动标签列表的设计原则
  2. Scroll容器的基本用法和参数设置
  3. Row组件在标签列表中的应用
  4. 水平滚动的实现原理
  5. 标签列表的样式优化技巧
  6. 标签列表的交互优化方法
  7. 标签列表的扩展功能
  8. 标签列表组件的封装与复用

掌握这些知识点后,你可以设计出流畅、美观、易用的水平滚动标签列表,提升应用的用户体验。在实际开发中,可以根据具体需求调整标签样式、布局和功能,创建符合应用设计风格的标签导航界面。


全栈若城
1 声望2 粉丝