HarmonyOS7 横向滚动分类栏:ArkUI/ArkTS 实战拆解

手绘笔记风信息图,主题是 HarmonyOS7 横向滚动分类栏实战拆解。画面中心是一条横向可滚动的餐

前言

横向分类栏看起来简单,实际很容易写得别扭:选中态不明显、内容宽度不稳定、分类多了之后首尾被裁掉。我写 HarmonyOS7 分类栏时,会先保证三件事:可横滑、选中态清晰、切换后内容同步刷新

分类栏不是装饰,它是列表数据的过滤入口,反馈必须快。

本文用“餐饮菜单分类”做例子。用户横向切换“早餐、午餐、咖啡、甜品”时,下方列表应该立即跟着变化;如果分类栏只是视觉组件,没有驱动数据刷新,就会让用户误以为点击没生效。

为什么这个问题经常被写乱

横向滚动分类栏 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。

设计要点

手绘流程图笔记风,主题是 HarmonyOS7 横向分类栏的实现步骤。用 5 个连续步骤框表示:1

细节建议
分类宽度根据文字自适应,保留左右 padding
选中态背景和文字颜色同时变化
内容刷新selectedId 驱动,不直接操作列表组件
首尾间距Scroll 内部 Row 负责统一间距

实现步骤

  1. 用数组维护分类,不把分类项写死在布局里。
  2. selectedId 作为唯一选中状态。
  3. 分类项宽度跟随文字,但左右 padding 保持一致。

手绘蓝图式框架图,主题是 HarmonyOS7 横向滚动分类栏的组件结构与状态驱动关系。画面分成上中

  1. 点击分类时同时更新选中态和内容列表。
  2. 分类很多时关闭滚动条,但保留首尾安全间距。

ArkUI/ArkTS 示例

class CategoryItem {
  id: number = 0
  name: string = ''

  constructor(id: number, name: string) {
    this.id = id
    this.name = name
  }
}

class FoodItem {
  id: number = 0
  categoryId: number = 0
  name: string = ''
  price: string = ''

  constructor(id: number, categoryId: number, name: string, price: string) {
    this.id = id
    this.categoryId = categoryId
    this.name = name
    this.price = price
  }
}

@Entry
@Component
struct CategoryScrollPage {
  @State selectedId: number = 1
  private categories: CategoryItem[] = [
    new CategoryItem(1, '全部'), new CategoryItem(2, '早餐'), new CategoryItem(3, '午餐'),
    new CategoryItem(4, '咖啡'), new CategoryItem(5, '甜品'), new CategoryItem(6, '夜宵')
  ]
  private foods: FoodItem[] = [
    new FoodItem(1, 2, '鸡蛋三明治', '¥18'),
    new FoodItem(2, 3, '照烧鸡腿饭', '¥32'),
    new FoodItem(3, 4, '冰美式', '¥16'),
    new FoodItem(4, 5, '芝士蛋糕', '¥22')
  ]

  private filteredFoods(): FoodItem[] {
    if (this.selectedId === 1) {
      return this.foods
    }
    return this.foods.filter((item: FoodItem) => item.categoryId === this.selectedId)
  }

  @Builder
  CategoryChip(item: CategoryItem) {
    Text(item.name)
      .fontSize(14)
      .fontColor(this.selectedId === item.id ? '#FFFFFF' : '#333333')
      .padding({ left: 14, right: 14, top: 8, bottom: 8 })
      .backgroundColor(this.selectedId === item.id ? '#1F6FEB' : '#FFFFFF')
      .borderRadius(18)
      .onClick(() => { this.selectedId = item.id })
  }

  build() {
    Column({ space: 14 }) {
      Text('菜单分类').fontSize(24).fontWeight(FontWeight.Bold)
      Scroll() {
        Row({ space: 8 }) {
          ForEach(this.categories, (item: CategoryItem) => {
            this.CategoryChip(item)
          }, (item: CategoryItem) => item.id.toString())
        }
        .padding({ left: 16, right: 16 })
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)

      List({ space: 8 }) {
        ForEach(this.filteredFoods(), (food: FoodItem) => {
          ListItem() {
            Row() {
              Text(food.name).fontSize(16).fontWeight(FontWeight.Medium)
              Blank()
              Text(food.price).fontSize(15).fontColor('#D9480F')
            }
            .padding(14)
            .backgroundColor('#FFFFFF')
            .borderRadius(10)
          }
        }, (food: FoodItem) => food.id.toString())
      }
      .layoutWeight(1)
    }
    .padding({ top: 16 })
    .backgroundColor('#F5F6FA')
    .height('100%')
  }
}

关键代码说明

ScrollDirection.Horizontal 明确横向滚动,配合 Row 控制分类之间的间距。

selectedId 是唯一选择状态。不要给每个分类都维护一个 selected,否则同步成本很高。

选中态同时改变文字和背景,比只改颜色更容易被用户识别。

filteredFoods() 让内容跟着分类状态派生出来。真实项目里这里可以替换成接口请求,但入口仍然应该是 selectedId 变化。

常见坑

  • 分类项文字长短不一时,不要固定同一个宽度,否则短文案会显得松散,长文案会被截断。
  • 横向滚动栏要有左右 padding,首尾分类贴边会显得很粗糙。
  • 切换分类后如果列表为空,要显示空状态,不要留一片空白。
  • 分类栏只是筛选入口,不建议在每个分类项里直接写复杂请求逻辑。

分类栏的状态边界

写横向分类栏时,我会把它当成一个小型筛选器,而不是单纯的一排按钮。分类项只负责把 selectedId 改成自己的 id,真正的数据变化交给 filteredFoods() 这类派生方法。这样做的好处是,后面无论改成本地筛选还是接口请求,分类栏本身都不用大改。

新手容易踩的坑,是在每个分类项里同时写选中样式、请求逻辑和列表刷新。短期看能跑,分类一多就会变得难维护。更稳的做法是让点击事件只表达“用户选了哪个分类”,其他逻辑围绕这个状态展开。

如果分类来自后端,还要特别注意 id 的稳定性。ForEach 的 key 不要用下标,分类顺序调整后下标会变,选中态和滚动复用都可能出现怪问题。

小结

HarmonyOS7 横向分类栏要写得舒服,重点不是组件多复杂,而是状态和视觉反馈稳定。分类越多,越要让用户明确知道自己当前选中了什么。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值