Grid 双栏网格与 List 单栏列表的视图切换实现

前言

在内容型应用中,视图切换是提升用户体验的重要功能。用户可以根据自己的偏好选择网格浏览或列表浏览。"海风日记"的首页支持双栏网格(Grid)和单栏列表(List)两种视图模式,通过右上角的切换按钮实现一键切换。

本文将从 HomeTab.ets 源码出发,深入讲解 Grid 和 List 组件的完整配置、视图切换的实现机制以及数据驱动渲染的最佳实践。


一、Grid 与 List 组件概述

1.1 Grid 组件

Grid 用于实现网格布局,支持自定义列数、行数和间距:

typescript 复制代码
Grid() {
  ForEach(this.diaryList, (item: DiaryEntry) => {
    GridItem() {
      DiaryCard({ ... })
    }
  })
}
.columnsTemplate('1fr 1fr')    // 双栏
.columnsGap(10)                 // 列间距
.rowsGap(10)                    // 行间距
.padding({ left: 12, right: 12 })
.layoutWeight(1)

1.2 List 组件

List 用于实现列表布局,支持垂直或水平滚动:

typescript 复制代码
List({ space: 12 }) {
  ForEach(this.diaryList, (item: DiaryEntry) => {
    ListItem() {
      DiaryCard({ ... })
    }
  })
}
.padding({ left: 14, right: 14 })
.layoutWeight(1)
.scrollBar(BarState.Off)

1.3 Grid 与 List 的对比

对比维度 Grid List
布局方式 网格 单列
显示密度 高(一屏可展示更多内容)
适用场景 图片为主、快速浏览 文字为主、详细阅读
内容展示 单行显示更多信息 信息更完整

二、视图切换的实现

2.1 状态变量

typescript 复制代码
@Component
export struct HomeTab {
  @State isGridView: boolean = true   // 当前视图模式
  @State diaryList: DiaryEntry[] = MOCK_DIARIES  // 日记数据
}

2.2 切换按钮

typescript 复制代码
Button() {
  SymbolGlyph(
    this.isGridView
      ? $r('sys.symbol.square_grid_2x2')  // 网格图标
      : $r('sys.symbol.list_bullet')       // 列表图标
  )
  .fontSize(18)
  .fontColor(['#C8956A'])
}
.width(30).height(30)
.borderRadius(8)
.backgroundColor('rgba(245,166,35,0.10)')
.onClick(() => { this.isGridView = !this.isGridView })

2.3 条件渲染视图

typescript 复制代码
if (this.isGridView) {
  // 双栏网格
  Grid() {
    ForEach(this.diaryList, (item: DiaryEntry) => {
      GridItem() {
        DiaryCard({ ... })
      }
    })
  }
  .columnsTemplate('1fr 1fr')
  .columnsGap(10).rowsGap(10)
  .padding({ left: 12, right: 12, bottom: TAB_BAR_HEIGHT + 16 })
  .layoutWeight(1)
} else {
  // 单栏列表
  List({ space: 12 }) {
    ForEach(this.diaryList, (item: DiaryEntry) => {
      ListItem() {
        DiaryCard({ ... })
      }
    })
  }
  .padding({ left: 14, right: 14, bottom: TAB_BAR_HEIGHT + 16 })
  .layoutWeight(1)
  .scrollBar(BarState.Off)
}

三、Grid 组件的属性详解

3.1 columnsTemplate

typescript 复制代码
.columnsTemplate('1fr 1fr')  // 两列,每列等宽

columnsTemplate 使用 fr 单位来分配列宽:

模板 效果
'1fr 1fr' 两列等宽
'1fr 2fr' 第二列宽度是第一列的两倍
'repeat(3, 1fr)' 三列等宽

3.2 间距控制

typescript 复制代码
.columnsGap(10)  // 列间距 10vp
.rowsGap(10)     // 行间距 10vp

3.3 滚动控制

Grid 默认可滚动,无需额外配置。如果需要禁用滚动:

typescript 复制代码
Grid()
  .scrollBar(BarState.Off)  // 隐藏滚动条

四、List 组件的属性详解

4.1 间距

typescript 复制代码
List({ space: 12 })  // 列表项间距 12vp

4.2 滚动条

typescript 复制代码
List()
  .scrollBar(BarState.Off)  // 隐藏滚动条
  // 或 .scrollBar(BarState.Auto)  // 自动显示
  // 或 .scrollBar(BarState.On)    // 始终显示

4.3 列表项

typescript 复制代码
List() {
  ForEach(this.diaryList, (item: DiaryEntry) => {
    ListItem() {
      DiaryCard({ ... })
    }
  })
}

五、视图切换的动画效果

5.1 使用 animateTo

typescript 复制代码
.onClick(() => {
  animateTo({ duration: 300 }, () => {
    this.isGridView = !this.isGridView
  })
})

5.2 过渡动画

typescript 复制代码
// 视图切换时添加过渡动画
Grid()
  .transition({ type: TransitionType.Insert, scale: { x: 0, y: 0 } })
  .transition({ type: TransitionType.Delete, scale: { x: 0, y: 0 } })

六、数据驱动渲染

6.1 ForEach 的使用

typescript 复制代码
ForEach(
  this.diaryList,                    // 数据源
  (item: DiaryEntry) => {           // UI 构建函数
    GridItem() { DiaryCard({ ... }) }
  },
  (item: DiaryEntry) => item.id     // 键值生成函数
)

6.2 键值的重要性

键值用于标识列表项的唯一性,帮助 ArkUI 高效更新列表:

  • 键值必须是唯一的
  • 键值不建议使用数组索引
  • 建议使用数据 ID 作为键值

七、常见问题与排查

7.1 Grid 显示空白

问题:Grid 区域显示为空白。

原因 :Grid 没有设置 columnsTemplate,或数据源为空。

解决方案

typescript 复制代码
Grid()
  .columnsTemplate('1fr 1fr')  // 必须设置列模板

7.2 List 滚动条不消失

问题:设置了 scrollBar(BarState.Off) 但滚动条仍然显示。

原因:属性设置顺序错误。

解决方案 :确保 scrollBar 在布局属性之后设置。


总结

本文通过"海风日记"首页的源码,深入讲解了 Grid 和 List 组件的完整用法:

  1. Grid 组件:双栏网格布局的配置
  2. List 组件:单栏列表布局的配置
  3. 视图切换:@State + 条件渲染实现一键切换
  4. 属性详解:columnsTemplate、间距、滚动条
  5. 动画效果:animateTo 和过渡动画
  6. 数据驱动:ForEach 的数据绑定和键值管理

下一篇文章将深入讲解 ForEach 数据驱动渲染,敬请期待。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
xianjixiance_6 小时前
@Entry 与 @Component 装饰器:组件声明、导出与页面入口
后端
w139548564227 小时前
Shadow 阴影体系:多层级阴影提升视觉层次
后端
IT_陈寒7 小时前
JavaScript的异步地狱里,我的Promise掉进了微任务陷阱
前端·人工智能·后端
xianjixiance_7 小时前
Swiper 轮播组件实现 3 页新用户引导
后端
CaffeinePro7 小时前
FastAPI高并发实战:缓存、分布式限流、链路日志三位一体架构
后端·fastapi
fengxinzi_zack8 小时前
年统计与最长连续天数:数据聚合可视化
后端
全堆鸿蒙8 小时前
aboutToAppear 生命周期:路由参数获取与数据初始化
后端
2501_931803758 小时前
Go 反射:原理、核心机制与实践指南
开发语言·后端·golang
程序员爱钓鱼8 小时前
Rust if let 与 while let 详解:简化模式匹配
前端·后端·rust