HarmonyOS应用<节气通>开发第21篇:CategoryGrid组件封装

引言

分类网格组件是应用中常见的UI元素,用于展示各种分类选项。本文将介绍如何封装一个CategoryGrid组件,用于展示分类列表。通过组件封装,可以:

  • 统一分类展示风格
  • 支持选中状态管理
  • 支持多种布局模式
  • 提高代码复用性

通过本文,你将掌握如何封装高质量的分类网格组件。


学习目标

完成本文后,你将能够:

  • ✅ 理解分类网格组件的设计原则
  • ✅ 封装CategoryGrid组件
  • ✅ 添加选中状态管理
  • ✅ 支持多种布局模式
  • ✅ 处理分类点击事件

需求分析

组件功能设计

功能 描述 技术要点
分类图标 展示分类图标 Image组件
分类名称 展示分类名称 Text组件
选中状态 显示选中效果 颜色变化、边框高亮
点击事件 处理分类选择 状态回调
布局模式 支持多种布局 网格、列表、横向滚动

核心实现

步骤1: 组件接口定义

typescript 复制代码
// components/CategoryGrid.ets

/**
 * 分类网格组件
 */
@Component
export struct CategoryGrid {
  // 分类列表
  @Prop categories: Category[] = [];
  
  // 选中的分类ID
  @Prop selectedId: string = '';
  
  // 布局模式
  @Prop layoutMode: LayoutMode = 'grid';
  
  // 列数(网格模式)
  @Prop columns: number = 4;
  
  // 点击事件
  @Prop onSelect: (id: string) => void = () => {};
  
  /**
   * 构建UI
   */
  build() {
    if (this.layoutMode === 'horizontal') {
      this.buildHorizontalLayout();
    } else if (this.layoutMode === 'list') {
      this.buildListLayout();
    } else {
      this.buildGridLayout();
    }
  }
}

type LayoutMode = 'grid' | 'horizontal' | 'list';

interface Category {
  id: string;
  name: string;
  icon: string | Resource;
  badge?: number;
}

设计要点:

  • 支持三种布局模式:grid(网格)、horizontal(横向滚动)、list(列表)
  • 支持选中状态管理
  • 支持徽章显示

步骤2: 网格布局模式

typescript 复制代码
/**
 * 构建网格布局
 */
@Builder
buildGridLayout(): void {
  Grid() {
    ForEach(this.categories, (category: Category) => {
      GridItem() {
        this.buildCategoryItem(category)
      }
    }, (category: Category) => category.id)
  }
  .columnsTemplate(this.getColumnTemplate())
  .rowsGap(16)
}

/**
 * 获取列模板
 */
private getColumnTemplate(): string {
  return Array(this.columns).fill('1fr').join(' ');
}

/**
 * 构建分类项
 */
@Builder
buildCategoryItem(category: Category): void {
  const isSelected = this.selectedId === category.id;
  
  Column({ space: 8 }) {
    // 图标区域
    Stack({ alignContent: Alignment.Center }) {
      Circle()
        .width(48)
        .height(48)
        .fillColor(isSelected ? '#4A9B6D' : '#F5F5F5')
      
      Image(typeof category.icon === 'string' ? $r('app.media.' + category.icon) : category.icon)
        .width(24)
        .height(24)
        .fillColor(isSelected ? '#FFFFFF' : '#666666')
      
      // 徽章
      if (category.badge && category.badge > 0) {
        Stack({ alignContent: Alignment.Center }) {
          Circle()
            .width(18)
            .height(18)
            .fillColor('#FF5252')
          
          Text(category.badge.toString())
            .fontSize(10)
            .fontColor('#FFFFFF')
        }
        .position({ right: -6, top: -6 })
      }
    }
    
    // 名称
    Text(category.name)
      .fontSize(13)
      .fontColor(isSelected ? '#4A9B6D' : '#666666')
      .fontWeight(isSelected ? FontWeight.Medium : FontWeight.Normal)
  }
  .width('100%')
  .alignItems(HorizontalAlign.Center)
  .onClick(() => {
    this.onSelect(category.id);
  })
}

设计要点:

  • 圆形图标背景
  • 选中状态颜色变化
  • 徽章显示(右上角小红点)

步骤3: 横向滚动布局

typescript 复制代码
/**
 * 构建横向滚动布局
 */
@Builder
buildHorizontalLayout(): void {
  Scroll({ scrollX: true, scrollBar: BarState.Off }) {
    Row({ space: 16 }) {
      ForEach(this.categories, (category: Category) => {
        this.buildHorizontalItem(category)
      }, (category: Category) => category.id)
    }
    .padding({ left: 16, right: 16 })
  }
  .width('100%')
}

/**
 * 构建横向布局的分类项
 */
@Builder
buildHorizontalItem(category: Category): void {
  const isSelected = this.selectedId === category.id;
  
  Column({ space: 6 }) {
    Stack({ alignContent: Alignment.Center }) {
      Circle()
        .width(56)
        .height(56)
        .fillColor(isSelected ? '#4A9B6D' : '#FFFFFF')
        .stroke(isSelected ? '#4A9B6D' : '#EEEEEE', 1)
      
      Image(typeof category.icon === 'string' ? $r('app.media.' + category.icon) : category.icon)
        .width(28)
        .height(28)
        .fillColor(isSelected ? '#FFFFFF' : '#666666')
    }
    
    Text(category.name)
      .fontSize(13)
      .fontColor(isSelected ? '#4A9B6D' : '#666666')
  }
  .width(80)
  .alignItems(HorizontalAlign.Center)
  .onClick(() => {
    this.onSelect(category.id);
  })
}

设计要点:

  • 横向滚动容器
  • 卡片式布局
  • 选中状态边框高亮

步骤4: 列表布局模式

typescript 复制代码
/**
 * 构建列表布局
 */
@Builder
buildListLayout(): void {
  Column({ space: 0 }) {
    ForEach(this.categories, (category: Category) => {
      this.buildListItem(category)
    }, (category: Category) => category.id)
  }
}

/**
 * 构建列表项
 */
@Builder
buildListItem(category: Category): void {
  const isSelected = this.selectedId === category.id;
  
  Row({ space: 12 }) {
    // 图标
    Stack({ alignContent: Alignment.Center }) {
      Circle()
        .width(40)
        .height(40)
        .fillColor(isSelected ? '#E8F5E9' : '#F5F5F5')
      
      Image(typeof category.icon === 'string' ? $r('app.media.' + category.icon) : category.icon)
        .width(20)
        .height(20)
        .fillColor(isSelected ? '#4A9B6D' : '#666666')
    }
    
    // 名称
    Text(category.name)
      .fontSize(15)
      .fontColor(isSelected ? '#4A9B6D' : '#333333')
      .flexGrow(1)
    
    // 选中标记
    if (isSelected) {
      Image($r('app.media.ic_check'))
        .width(20)
        .height(20)
        .fillColor('#4A9B6D')
    }
    
    // 箭头
    Image($r('app.media.ic_arrow_right'))
      .width(16)
      .height(16)
      .fillColor('#CCCCCC')
  }
  .width('100%')
  .height(56)
  .padding({ left: 16, right: 16 })
  .backgroundColor('#FFFFFF')
  .onClick(() => {
    this.onSelect(category.id);
  })
}

设计要点:

  • 列表布局
  • 左侧图标+中间文字+右侧箭头
  • 选中状态勾选标记

步骤5: 使用CategoryGrid组件

typescript 复制代码
// 在页面中使用CategoryGrid组件

@Entry
@Component
struct CategoryPage {
  @State selectedCategory: string = '';
  
  @State categories: Category[] = [
    { id: 'all', name: '全部', icon: 'ic_all' },
    { id: 'solar', name: '节气', icon: 'ic_solar', badge: 24 },
    { id: 'article', name: '文章', icon: 'ic_article', badge: 128 },
    { id: 'quiz', name: '测验', icon: 'ic_quiz', badge: 12 },
    { id: 'recipe', name: '食谱', icon: 'ic_recipe' },
    { id: 'health', name: '养生', icon: 'ic_health' },
    { id: 'custom', name: '习俗', icon: 'ic_custom' },
    { id: 'poem', name: '诗词', icon: 'ic_poem' }
  ];
  
  build() {
    Column({ space: 16 }) {
      // 网格布局示例
      Text('网格布局')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .padding({ left: 16 })
      
      CategoryGrid({
        categories: this.categories,
        selectedId: this.selectedCategory,
        layoutMode: 'grid',
        columns: 4,
        onSelect: (id: string) => {
          this.selectedCategory = id;
        }
      })
      
      // 横向滚动布局示例
      Text('横向滚动布局')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .padding({ left: 16 })
      
      CategoryGrid({
        categories: this.categories,
        selectedId: this.selectedCategory,
        layoutMode: 'horizontal',
        onSelect: (id: string) => {
          this.selectedCategory = id;
        }
      })
    }
    .width('100%')
    .backgroundColor('#F8F7F2')
  }
}

interface Category {
  id: string;
  name: string;
  icon: string | Resource;
  badge?: number;
}

设计要点:

  • 在页面中使用不同布局模式
  • 绑定选中状态
  • 处理分类选择事件

本章小结

核心知识点

本文完成了CategoryGrid组件的封装:

1. 组件属性设计

  • categories: 分类列表
  • selectedId: 选中的分类ID
  • layoutMode: 布局模式(grid/horizontal/list)
  • columns: 列数(网格模式)
  • onSelect: 点击事件回调

2. 三种布局模式

  • 网格布局:适合首页快速浏览
  • 横向滚动布局:适合分类导航
  • 列表布局:适合设置页面

3. 选中状态管理

  • 颜色变化
  • 边框高亮
  • 勾选标记

下一步预告

CategoryGrid组件已经完成!在下一篇文章中,我们将学习:

  • HolidayCard组件封装
  • 节日卡片展示
  • 日期高亮
  • 节日类型标识

节气通应用已发布上线,可在应用市场下载体验


相关链接

相关推荐
李游Leo5 分钟前
HarmonyOS 7 实战开发 05:把页面做到可上线状态
ios·harmonyos
zhangfeng11333 小时前
ATK(华为算子测试平台)详细介绍 CANN(Compute Architecture for Neural Networks,神经网络计算架构
人工智能·华为·ai编程·npu·cann
贾伟康3 小时前
【HarmonyOS 7新能力|043】可变字体工程封装:把接入逻辑放进可维护的分层结构
harmonyos·arkts·arkui·ui设计·可变字体
HarmonyOS_SDK3 小时前
HarmonyOS智慧多窗,让应用在任意窗口都“恰到好处”
harmonyos
zhangfeng11334 小时前
《从“人工适配“到“智能生成“:KernelSwift 跨国产芯片算子迁移全栈方案解读》 —— 强调范式跃迁和跨硬件属性,适合偏架构分析的写法
人工智能·算法·华为·ai编程·npu
传奇开心果编程6 小时前
【ArkUI 练中学】第14课:应用性能优化实战
学习·ui·华为·harmonyos
用户593096009786 小时前
Flutter 鸿蒙化实战:flutter_tts 适配 OpenHarmony,文本转语音
harmonyos
威哥爱编程9 小时前
HarmonyOS 互动卡片实战:摇一摇触发静态转动态,前景元素出框
华为·harmonyos·arkts
威哥爱编程9 小时前
HarmonyOS 7 闪控窗实战:标准悬浮窗、侧边栏暂存与闪控球一键切换
华为·harmonyos·arkts
威哥爱编程9 小时前
HarmonyOS 7 图像超分实战:端侧 4 倍高清放大,数据不出设备
华为·harmonyos·arkts