在这里插入图片描述

社团分类页面按类型展示社团,用户可以快速找到感兴趣的社团类别。这篇文章带大家实现社团分类模块。

页面整体设计

社团分类页面使用网格布局展示各个分类,每个分类卡片显示图标、名称和社团数量。

页面采用StatelessWidget实现:

class ClubCategoryPage extends StatelessWidget {
  const ClubCategoryPage({super.key});

页面不需要维护内部状态,数据从Provider获取。
const构造函数可以让Flutter复用Widget实例。

导入依赖包

在文件开头导入必要的依赖:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

material.dart提供Material Design风格的组件。
provider用于状态管理和数据共享。

导入项目内部文件:

import '../../providers/app_provider.dart';
import 'club_detail_page.dart';

app_provider包含社团列表数据。
club_detail_page是社团详情页面。

定义分类数据

在build方法中定义分类配置:

  
  Widget build(BuildContext context) {
    final categories = [
      {
        'name': '科技', 
        'icon': Icons.computer, 
        'color': Colors.blue
      },
      {
        'name': '艺术', 
        'icon': Icons.palette, 
        'color': Colors.purple
      },
      {
        'name': '体育', 
        'icon': Icons.sports_basketball, 
        'color': Colors.orange
      },

用List存储分类配置,每个分类包含名称、图标和颜色。
这种数据驱动的方式便于维护和扩展。

更多分类:

      {
        'name': '学术', 
        'icon': Icons.school, 
        'color': Colors.green
      },
      {
        'name': '公益', 
        'icon': Icons.volunteer_activism, 
        'color': Colors.red
      },
      {
        'name': '文娱', 
        'icon': Icons.movie, 
        'color': Colors.pink
      },
    ];

六个分类涵盖了常见的社团类型。
每个分类用不同颜色区分,视觉上更丰富。

构建页面骨架

使用Scaffold搭建基本结构:

    return Scaffold(
      appBar: AppBar(
        title: const Text('社团分类')
      ),

Scaffold提供标准的Material页面结构。
AppBar显示页面标题"社团分类"。

监听数据变化

页面主体使用Consumer监听数据:

      body: Consumer<AppProvider>(
        builder: (context, provider, _) {

Consumer自动订阅AppProvider的变化。
当社团数据变化时会自动更新分类下的社团数量。

网格布局

使用GridView.builder构建网格:

          return GridView.builder(
            padding: const EdgeInsets.all(16),
            gridDelegate: 
                const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 12,
              mainAxisSpacing: 12,
              childAspectRatio: 1.2,
            ),
            itemCount: categories.length,

crossAxisCount设为2表示每行显示2个卡片。
crossAxisSpacing和mainAxisSpacing设置卡片间距。

childAspectRatio设为1.2让卡片稍微宽一些。
这个比例在手机屏幕上显示效果较好。

构建分类卡片

itemBuilder回调构建每个分类卡片:

            itemBuilder: (context, index) {
              final category = categories[index];
              final clubCount = provider.clubs
                  .where((c) => c.category == category['name'])
                  .length;

从配置中取出当前分类信息。
统计该分类下的社团数量。

卡片容器:

              return Card(
                child: InkWell(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (_) => _CategoryClubsPage(
                          category: category['name'] as String,
                          color: category['color'] as Color,
                        ),
                      ),
                    );
                  },

Card提供Material风格的卡片效果。
点击后跳转到该分类的社团列表页面。

卡片内容

卡片内部纵向排列图标、名称和数量:

                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Container(
                        padding: const EdgeInsets.all(16),
                        decoration: BoxDecoration(
                          color: (category['color'] as Color)
                              .withOpacity(0.1),
                          shape: BoxShape.circle,
                        ),
                        child: Icon(
                          category['icon'] as IconData, 
                          color: category['color'] as Color, 
                          size: 32
                        ),
                      ),

图标放在圆形背景容器中。
背景色是分类颜色的10%透明度版本。

名称和数量:

                      const SizedBox(height: 12),
                      Text(
                        category['name'] as String, 
                        style: const TextStyle(
                          fontWeight: FontWeight.bold, 
                          fontSize: 16
                        )
                      ),
                      const SizedBox(height: 4),
                      Text(
                        '$clubCount个社团', 
                        style: const TextStyle(
                          color: Colors.grey, 
                          fontSize: 12
                        )
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

分类名称用粗体16像素字号。
社团数量用灰色小字显示。

分类社团列表页

定义分类社团列表页面:

class _CategoryClubsPage extends StatelessWidget {
  final String category;
  final Color color;

  const _CategoryClubsPage({
    required this.category, 
    required this.color
  });

页面接收分类名称和颜色作为参数。
下划线前缀表示这是私有类。

构建分类列表页

使用Scaffold搭建结构:

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$category类社团')
      ),
      body: Consumer<AppProvider>(
        builder: (context, provider, _) {
          final clubs = provider.clubs
              .where((c) => c.category == category)
              .toList();

标题显示分类名称加"类社团"后缀。
where方法筛选出该分类的社团。

空状态处理

没有社团时显示提示:

          if (clubs.isEmpty) {
            return const Center(
              child: Text(
                '暂无该类社团', 
                style: TextStyle(color: Colors.grey)
              )
            );
          }

空状态处理是用户体验的重要环节。
灰色文字提示用户当前没有数据。

社团列表

使用ListView.builder构建列表:

          return ListView.builder(
            padding: const EdgeInsets.all(16),
            itemCount: clubs.length,
            itemBuilder: (context, index) {
              final club = clubs[index];
              return Card(
                margin: const EdgeInsets.only(bottom: 12),
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: color.withOpacity(0.1),
                    child: Text(
                      club.name[0], 
                      style: TextStyle(
                        color: color, 
                        fontWeight: FontWeight.bold
                      )
                    ),
                  ),

头像使用分类颜色作为背景。
显示社团名称的首字母。

社团信息:

                  title: Text(club.name),
                  subtitle: Text(
                    '${club.memberCount}人  评分${club.rating}'
                  ),
                  trailing: const Icon(Icons.chevron_right),
                  onTap: () => Navigator.push(
                    context, 
                    MaterialPageRoute(
                      builder: (_) => ClubDetailPage(club: club)
                    )
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

显示社团名称、成员数和评分。
点击后跳转到社团详情页面。

网格布局的优势

网格布局让分类一目了然。
用户可以快速浏览所有分类。

每行两个卡片在手机屏幕上显示效果好。
卡片大小适中,点击方便。

颜色区分的意义

每个分类用不同颜色区分有几个好处。
首先视觉上更丰富,不会显得单调。

其次帮助用户记忆和识别分类。
比如蓝色代表科技,橙色代表体育。

社团数量的展示

显示每个分类下的社团数量很有用。
用户可以了解各分类的社团分布情况。

数量多的分类可能更活跃。
用户可以据此选择感兴趣的分类。

分类配置的扩展

当前使用硬编码的分类配置。
实际项目中可以从服务器获取分类列表。

这样可以动态调整分类。
比如添加新分类或修改分类图标。

搜索功能

可以在分类页面添加搜索:

AppBar(
  title: const Text('社团分类'),
  actions: [
    IconButton(
      icon: Icon(Icons.search),
      onPressed: () {
        // 打开搜索
      },
    ),
  ],
)

搜索按钮放在AppBar右侧。
用户可以直接搜索社团名称。

热门分类

可以突出显示热门分类:

if (isHot)
  Positioned(
    right: 8,
    top: 8,
    child: Container(
      padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
      decoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.circular(4),
      ),
      child: Text('热门', style: TextStyle(color: Colors.white, fontSize: 10)),
    ),
  )

热门标签显示在卡片右上角。
红色背景白色文字很醒目。

小结

社团分类页面通过网格布局展示各个分类,每个卡片显示图标、名称和社团数量。不同分类用不同颜色区分,视觉效果丰富。点击分类可进入该分类的社团列表页面。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐