一、项目概述

原项目文章:https://blog.csdn.net/2301_80035882/article/details/155078013?fromshare=blogdetail&sharetype=blogdetail&sharerId=155078013&sharerefer=PC&sharesource=2301_80035882&sharefrom=from_linkhttps://blog.csdn.net/2301_80035882/article/details/155078013?fromshare=blogdetail&sharetype=blogdetail&sharerId=155078013&sharerefer=PC&sharesource=2301_80035882&sharefrom=from_link

本次更新为 GitCode 口袋工具项目添加了以下核心功能:

  • 自定义仓库列表卡片组件 - 封装了美观的仓库信息展示卡片

  • 自定义用户列表卡片组件 - 封装了美观的用户信息展示卡片

  • 下拉刷新功能 - 支持下拉手势刷新数据

  • 上拉加载更多 - 支持上拉手势加载更多数据

  • 数据分页管理 - 完整的分页加载逻辑实现(支持仓库和用户)

这些功能提升了用户体验,使应用能够高效地展示和浏览大量仓库和用户数据。


二、功能特性

2.1 核心功能

1. 自定义仓库卡片组件 (RepositoryCard)

  • 📦 信息展示:仓库名称、描述、编程语言、Star 数、Fork 数、更新时间

  • 🎨 视觉设计:Material Design 3 风格,圆角卡片,支持点击反馈

  • 🌈 语言颜色:根据编程语言自动显示不同颜色标识

  • 📊 数字格式化:自动将大数字格式化为易读形式(如 1.5k)

  • 🔒 私有标识:区分公开和私有仓库的图标显示

2. 分页列表页面 (RepositoryListPage)

  • 🔄 下拉刷新:支持下拉手势刷新当前数据

  • ⬆️ 上拉加载:滚动到底部自动加载更多数据

  • 📄 分页管理:自动管理页码,每页加载 20 条数据

  • ⚠️ 错误处理:完善的错误提示和重试机制

  • 🎯 状态管理:加载中、空状态、错误状态的完整处理

3. 自定义用户卡片组件 (UserCard)

  • 👤 信息展示:用户头像、名称、登录名、注册时间、主页地址

  • 🎨 视觉设计:Material Design 3 风格,圆角卡片,支持点击反馈

  • 📅 日期格式化:自动将 ISO 日期格式化为易读形式(YYYY-MM-DD)

  • 🔗 链接展示:用户主页地址以蓝色链接形式展示

  • 📐 固定高度:卡片高度固定为 150px,确保列表整齐

4. 用户分页列表页面 (UserListPage)

  • 🔄 下拉刷新:支持下拉手势刷新当前数据

  • ⬆️ 上拉加载:滚动到底部自动加载更多数据

  • 📄 分页管理:自动管理页码,每页加载 20 条数据

  • ⚠️ 错误处理:完善的错误提示和重试机制

  • 🎯 状态管理:加载中、空状态、错误状态的完整处理

5. 主页面集成

  • 🔍 搜索结果预览:使用新卡片组件展示搜索结果(仓库和用户)

  • 🔗 快速跳转:一键跳转到完整列表页面(支持仓库和用户)

  • 🎨 统一风格:保持整体 UI 风格一致性


2.2 技术架构

技术栈

  • Flutter SDK: ^3.6.2

  • pull_to_refresh: ^2.0.0 - 下拉刷新和上拉加载库

  • dio: ^5.7.0 - HTTP 请求库

  • Material Design 3: 现代化 UI 设计

架构设计

┌─────────────────────────────────────────┐
│          GitCodePocketToolPage          │
│           (主页面)                      │
│  ┌───────────────────────────────────┐  │
│  │ 搜索结果预览                       │  │
│  │  ├─ RepositoryCard (仓库卡片)      │  │
│  │  └─ UserCard (用户卡片)            │  │
│  │         ↓ 点击查看全部             │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
         ↓                    ↓
┌───────────────────┐  ┌─────────────────────┐
│ RepositoryListPage│  │  UserListPage       │
│   (仓库列表页)     │  │  (用户列表页)        │
│  ┌──────────────┐ │  │  ┌───────────────┐  │
│  │SmartRefresher│ │  │  │SmartRefresher │  │
│  │ ┌──────────┐ │ │  │  │ ┌────────┐    │  │
│  │ │ListView  │ │ │  │  │ │ListView│    │  │
│  │ │Repository│ │ │  │  │ │UserCard│    │  │
│  │ └──────────┘ │ │  │  │ └────────┘    │  │
│  └──────────────┘ │  │  └───────────────┘  │
└───────────────────┘  └─────────────────────┘
         ↓                    ↓
┌─────────────────────────────────────────┐
│        GitCodeApiClient (API层)         │
│  ┌───────────────────────────────────┐  │
│  │ searchRepositories(page, perPage) │  │
│  │ searchUsers(page, perPage)        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘


2.3 依赖说明

新增依赖

pubspec.yaml 中添加了以下依赖:

dependencies:
  pull_to_refresh: ^2.0.0

依赖安装

运行以下命令安装依赖:

flutter pub get

依赖说明

pull_to_refresh 是一个功能强大的 Flutter 下拉刷新和上拉加载库,提供了:

  • 下拉刷新功能

  • 上拉加载更多功能

  • 自定义刷新头部和底部

  • 多种刷新样式支持

  • 完善的加载状态管理


2.4 代码实现详解

1. 自定义仓库卡片组件

文件路径: lib/widgets/repository_card.dart

组件结构
class RepositoryCard extends StatelessWidget {
  const RepositoryCard({
    super.key,
    required this.repository,
    this.onTap,
  });
​
  final GitCodeRepository repository;
  final VoidCallback? onTap;
}
核心功能实现
1.1 卡片布局

组件使用 Card + InkWell 实现可点击的卡片效果:

Card(
  elevation: 2,
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  child: InkWell(
    onTap: onTap,
    borderRadius: BorderRadius.circular(12),
    child: Padding(...),
  ),
)

设计要点

  • elevation: 2 - 适中的阴影效果

  • borderRadius: 12 - 圆角设计,符合 Material Design 3

  • InkWell - 提供点击波纹效果

1.2 仓库信息展示

仓库名称和图标

Row(
  children: [
    Icon(
      repository.isPrivate == true
          ? Icons.lock_outline
          : Icons.folder_outlined,
      color: theme.colorScheme.primary,
      size: 20,
    ),
    Expanded(
      child: Text(
        repository.fullName,
        style: theme.textTheme.titleMedium?.copyWith(
          fontWeight: FontWeight.bold,
        ),
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

描述信息

if (repository.description?.isNotEmpty == true)
  Text(
    repository.description!,
    style: theme.textTheme.bodyMedium?.copyWith(
      color: Colors.grey[700],
    ),
    maxLines: 2,
    overflow: TextOverflow.ellipsis,
  ),

1.3 统计信息展示

使用 _buildStatItem 方法构建统计项:

Widget _buildStatItem({
  required IconData icon,
  required String label,
  Color? color,
}) {
  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      Icon(icon, size: 16, color: color ?? Colors.grey[600]),
      const SizedBox(width: 4),
      Text(
        label,
        style: TextStyle(
          fontSize: 12,
          color: color ?? Colors.grey[700],
        ),
      ),
    ],
  );
}

统计项包括

  • 编程语言(带颜色标识)

  • Star 数量

  • Fork 数量

1.4 语言颜色映射
Color _getLanguageColor(String language) {
  final colors = <String, Color>{
    'Dart': Colors.blue,
    'Java': Colors.orange,
    'JavaScript': Colors.yellow[700]!,
    'Python': Colors.blue[300]!,
    'Go': Colors.cyan,
    'TypeScript': Colors.blue[800]!,
    'C++': Colors.pink,
    'C': Colors.grey[800]!,
    'Swift': Colors.orange[300]!,
    'Kotlin': Colors.purple,
    'Rust': Colors.orange[900]!,
  };
  return colors[language] ?? Colors.grey[600]!;
}
支持的编程语言颜色
  • 为常见编程语言设置了专属颜色

  • 未匹配的语言使用默认灰色

1.5 数字格式化
String _formatNumber(int number) {
  if (number >= 1000) {
    return '${(number / 1000).toStringAsFixed(1)}k';
  }
  return number.toString();
}

格式化规则

  • 小于 1000:直接显示数字

  • 大于等于 1000:转换为 k 单位(如 1500 → 1.5k)


2. 仓库分页列表页面

文件路径: lib/pages/repository_list_page.dart

页面结构
class RepositoryListPage extends StatefulWidget {
  const RepositoryListPage({
    super.key,
    required this.keyword,
    required this.token,
  });

  final String keyword;
  final String token;
}
状态管理
class _RepositoryListPageState extends State<RepositoryListPage> {
  final RefreshController _refreshController = RefreshController(
    initialRefresh: false,
  );
  final GitCodeApiClient _client = GitCodeApiClient();

  List<GitCodeRepository> _repositories = [];
  int _currentPage = 1;
  final int _perPage = 20;
  bool _hasMore = true;
  String? _errorMessage;
  bool _isLoading = false;
}

状态变量说明

  • _refreshController: 刷新控制器,管理下拉刷新和上拉加载状态

  • _repositories: 仓库列表数据

  • _currentPage: 当前页码

  • _perPage: 每页数据量(固定为 20)

  • _hasMore: 是否还有更多数据

  • _errorMessage: 错误信息

  • _isLoading: 加载状态

核心方法实现
2.1 数据加载方法
Future<void> _loadRepositories({bool refresh = false}) async {
  if (_isLoading) return;

  if (refresh) {
    _currentPage = 1;
    _hasMore = true;
    _repositories.clear();
  }

  if (!_hasMore) {
    _refreshController.loadNoData();
    return;
  }

  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });

  try {
    final repos = await _client.searchRepositories(
      keyword: widget.keyword,
      personalToken: widget.token,
      perPage: _perPage,
      page: _currentPage,
    );

    setState(() {
      if (refresh) {
        _repositories = repos;
      } else {
        _repositories.addAll(repos);
      }

      // 判断是否还有更多数据
      _hasMore = repos.length >= _perPage;
      _currentPage++;
      _errorMessage = null;
      _isLoading = false;
    });

    // 更新刷新控制器状态
    if (refresh) {
      _refreshController.refreshCompleted();
    } else {
      if (_hasMore) {
        _refreshController.loadComplete();
      } else {
        _refreshController.loadNoData();
      }
    }
  } on GitCodeApiException catch (e) {
    // 错误处理...
  }
}

方法逻辑

  1. 防重复加载:如果正在加载,直接返回

  2. 刷新模式:重置页码和数据列表

  3. 数据加载:调用 API 获取数据

  4. 数据合并

    • 刷新模式:替换所有数据

    • 加载更多:追加到现有数据

  5. 状态更新:更新 UI 和刷新控制器状态

2.2 刷新和加载回调
/// 下拉刷新
void _onRefresh() {
  _loadRepositories(refresh: true);
}

/// 上拉加载
void _onLoading() {
  _loadRepositories(refresh: false);
}
2.3 UI 构建

SmartRefresher 配置

SmartRefresher(
  controller: _refreshController,
  enablePullDown: true,
  enablePullUp: _hasMore,
  header: const ClassicHeader(
    refreshingText: '刷新中...',
    completeText: '刷新完成',
    idleText: '下拉刷新',
    releaseText: '释放刷新',
  ),
  footer: ClassicFooter(
    loadingText: '加载中...',
    noDataText: '没有更多数据了',
    idleText: '上拉加载更多',
    canLoadingText: '释放加载',
  ),
  onRefresh: _onRefresh,
  onLoading: _onLoading,
  child: ListView.builder(...),
)

配置说明

  • enablePullDown: true - 启用下拉刷新

  • enablePullUp: _hasMore - 根据是否有更多数据启用上拉加载

  • header - 自定义刷新头部提示文字

  • footer - 自定义加载底部提示文字

列表构建

ListView.builder(
  itemCount: _repositories.length,
  padding: const EdgeInsets.symmetric(vertical: 8),
  itemBuilder: (context, index) {
    return RepositoryCard(
      repository: _repositories[index],
      onTap: () {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('点击了: ${_repositories[index].fullName}'),
            duration: const Duration(seconds: 2),
          ),
        );
      },
    );
  },
)
2.4 错误处理

错误状态展示

_errorMessage != null && _repositories.isEmpty
  ? Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.error_outline, size: 64, color: Colors.red[300]),
          const SizedBox(height: 16),
          Text(_errorMessage!, style: TextStyle(color: Colors.red[700])),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () => _onRefresh(),
            child: const Text('重试'),
          ),
        ],
      ),
    )

空状态展示

_repositories.isEmpty
  ? Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.search_off, size: 64, color: Colors.grey[400]),
          const SizedBox(height: 16),
          Text('暂无数据', style: TextStyle(color: Colors.grey[600])),
        ],
      ),
    )

3. 自定义用户卡片组件

文件路径: lib/widgets/user_card.dart

组件结构
class UserCard extends StatelessWidget {
  const UserCard({
    super.key,
    required this.user,
    this.onTap,
    this.height,
  });
​
  final GitCodeSearchUser user;
  final VoidCallback? onTap;
  final double? height;
}
核心功能实现
3.1 卡片布局

组件使用 Card + InkWell + Row 布局,左侧显示头像,右侧显示用户信息:

Card(
  elevation: 2,
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  child: InkWell(
    onTap: onTap,
    borderRadius: BorderRadius.circular(12),
    child: Padding(
      padding: const EdgeInsets.all(12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 左侧头像
          CircleAvatar(...),
          // 右侧用户信息
          Expanded(
            child: Column(...),
          ),
        ],
      ),
    ),
  ),
)
3.2 用户信息展示

头像显示

CircleAvatar(
  radius: 20,
  backgroundImage: NetworkImage(user.avatarUrl),
  onBackgroundImageError: (_, __) {},
  backgroundColor: theme.colorScheme.surfaceVariant,
  child: user.avatarUrl.isEmpty
      ? Icon(Icons.person, ...)
      : null,
)

用户名称

Text(
  displayName, // user.name ?? user.login
  style: theme.textTheme.titleMedium?.copyWith(
    fontWeight: FontWeight.bold,
    fontSize: 16,
  ),
  maxLines: 1,
  overflow: TextOverflow.ellipsis,
)

登录名

Row(
  children: [
    Icon(Icons.person_outline, size: 14),
    Text(
      user.login,
      style: theme.textTheme.bodyMedium?.copyWith(
        fontSize: 13,
      ),
    ),
  ],
)

注册时间

Row(
  children: [
    Icon(Icons.calendar_today, size: 13),
    Text(
      '注册于 ${_formatDate(user.createdAt!)}',
      style: theme.textTheme.bodySmall?.copyWith(
        fontSize: 12,
      ),
    ),
  ],
)

主页地址

Row(
  children: [
    Icon(Icons.link, size: 13),
    Text(
      user.htmlUrl!,
      style: theme.textTheme.bodySmall?.copyWith(
        color: Colors.blue[600],
        fontSize: 12,
      ),
    ),
  ],
)
3.3 日期格式化
String _formatDate(String dateString) {
  try {
    final date = DateTime.parse(dateString);
    return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
  } catch (e) {
    return dateString.length > 10 ? dateString.substring(0, 10) : dateString;
  }
}

格式化规则

  • 尝试解析 ISO 8601 格式日期

  • 格式化为 YYYY-MM-DD 格式

  • 解析失败时返回原始字符串的前10个字符

3.4 固定高度设计

卡片使用固定高度设计,确保列表整齐:

// 固定每行的高度常量
static const double _avatarSize = 40.0;
static const double _titleRowHeight = 20.0;
static const double _loginRowHeight = 18.0;
static const double _infoRowHeight = 18.0;
static const double _urlRowHeight = 18.0;
static const double _spacing = 6.0;
static const double _padding = 12.0;

// 计算总高度
final calculatedHeight = _padding * 2 +
    _avatarSize +
    _spacing +
    _titleRowHeight +
    _spacing +
    _loginRowHeight +
    _spacing +
    _infoRowHeight +
    _spacing +
    _urlRowHeight +
    8.0; // 余量

4. 用户分页列表页面

文件路径: lib/pages/user_list_page.dart

页面结构
class UserListPage extends StatefulWidget {
  const UserListPage({
    super.key,
    required this.keyword,
    required this.token,
  });

  final String keyword;
  final String token;
}

状态管理

class _UserListPageState extends State<UserListPage> {
  final RefreshController _refreshController = RefreshController(
    initialRefresh: false,
  );
  final GitCodeApiClient _client = GitCodeApiClient();

  List<GitCodeSearchUser> _users = [];
  int _currentPage = 1;
  final int _perPage = 20;
  bool _hasMore = true;
  String? _errorMessage;
  bool _isLoading = false;
}

状态变量说明

  • _refreshController: 刷新控制器,管理下拉刷新和上拉加载状态

  • _users: 用户列表数据

  • _currentPage: 当前页码

  • _perPage: 每页数据量(固定为 20)

  • _hasMore: 是否还有更多数据

  • _errorMessage: 错误信息

  • _isLoading: 加载状态

核心方法实现
4.1 数据加载方法
Future<void> _loadUsers({bool refresh = false}) async {
  if (_isLoading) return;
​
  if (refresh) {
    _currentPage = 1;
    _hasMore = true;
    _users.clear();
  }
​
  if (!_hasMore) {
    _refreshController.loadNoData();
    return;
  }
​
  setState(() {
    _isLoading = true;
    _errorMessage = null;
  });
​
  try {
    final users = await _client.searchUsers(
      keyword: widget.keyword,
      personalToken: widget.token,
      perPage: _perPage,
      page: _currentPage,
    );
​
    setState(() {
      if (refresh) {
        _users = users;
      } else {
        _users.addAll(users);
      }
​
      _hasMore = users.length >= _perPage;
      _currentPage++;
      _errorMessage = null;
      _isLoading = false;
    });
​
    // 更新刷新控制器状态
    if (refresh) {
      _refreshController.refreshCompleted();
    } else {
      if (_hasMore) {
        _refreshController.loadComplete();
      } else {
        _refreshController.loadNoData();
      }
    }
  } on GitCodeApiException catch (e) {
    // 错误处理...
  }
}

方法逻辑

  1. 防重复加载:如果正在加载,直接返回

  2. 刷新模式:重置页码和数据列表

  3. 数据加载:调用 API 获取用户数据

  4. 数据合并

    • 刷新模式:替换所有数据

    • 加载更多:追加到现有数据

  5. 状态更新:更新 UI 和刷新控制器状态

4.2 UI 构建

列表构建

ListView.builder(
  itemCount: _users.length,
  padding: const EdgeInsets.symmetric(vertical: 8),
  itemBuilder: (context, index) {
    return UserCard(
      user: _users[index],
      onTap: () {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('点击了用户: ${_users[index].name ?? _users[index].login}'),
            duration: const Duration(seconds: 2),
          ),
        );
      },
    );
  },
)

用户列表页面的实现与仓库列表页面类似,都使用 SmartRefresher 实现下拉刷新和上拉加载功能。


5. 主页面集成

文件路径: lib/main.dart

导入新组件
import 'pages/repository_list_page.dart';
import 'pages/user_list_page.dart';
import 'widgets/repository_card.dart';
import 'widgets/user_card.dart';

修改仓库结果展示

原实现(使用 _RepoTile):

return Column(
  children: _repoResults.map(_RepoTile.new).toList(),
);

新实现(使用 RepositoryCard + 跳转按钮):

return Column(
  children: [
    // 使用新的自定义仓库卡片组件
    ..._repoResults.map(
      (repo) => RepositoryCard(
        repository: repo,
        onTap: () {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('点击了: ${repo.fullName}'),
              duration: const Duration(seconds: 2),
            ),
          );
        },
      ),
    ),
    const SizedBox(height: 16),
    // 添加查看全部按钮,跳转到带分页的列表页
    FilledButton.icon(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => RepositoryListPage(
              keyword: _keywordController.text,
              token: _tokenController.text.trim(),
            ),
          ),
        );
      },
      icon: const Icon(Icons.list),
      label: const Text('查看全部并加载更多'),
    ),
  ],
);

修改用户结果展示

原实现(使用 _UserTile):

return Column(
  children: _userResults.map(_UserTile.new).toList(),
);

新实现(使用 UserCard + 跳转按钮):

return Column(
  children: [
    // 使用新的自定义用户卡片组件
    ..._userResults.map(
      (user) => UserCard(
        user: user,
        onTap: () {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('点击了用户: ${user.name ?? user.login}'),
              duration: const Duration(seconds: 2),
            ),
          );
        },
      ),
    ),
    const SizedBox(height: 16),
    // 添加查看全部按钮,跳转到带分页的用户列表页
    FilledButton.icon(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => UserListPage(
              keyword: _keywordController.text,
              token: _tokenController.text.trim(),
            ),
          ),
        );
      },
      icon: const Icon(Icons.list),
      label: const Text('查看全部并加载更多'),
    ),
  ],
);

改进点

  1. 使用新的 RepositoryCardUserCard 组件,视觉效果更好

  2. 添加跳转按钮,可以查看完整列表(支持仓库和用户)

  3. 保持原有功能不变,向后兼容


三、使用指南

3.1 环境准备

【2025最新】Flutter 编译开发 鸿蒙HarmonyOS 6 项目教程(Windows)_flutter build app 鸿蒙-CSDN博客https://blog.csdn.net/2301_80035882/article/details/155001657?spm=1011.2415.3001.5331根据上述已经配置好环境。

3.2 下载仓库压缩包

仓库地址:

gitcode_pocket_tool - AtomGit | GitCodehttps://gitcode.com/byyixuan/gitcode_pocket_toolhttps://gitcode.com/byyixuan/gitcode_pocket_tool

3.3 配置访问令牌

GitCode - 全球开发者的开源社区,开源代码托管平台https://gitcode.com/setting/token-classichttps://gitcode.com/setting/token-classic

配置令牌名称和到期时间,之后向下滑找到新建访问令牌按钮,点击此按钮新建:

注意重要:新建成功后,一定要复制好这个访问令牌,并且保存好,不要让别人知道!

3.4 解压项目并打开

打开之后目录如图所示:

点击app_config.dart文件,并将你的访问令牌复制到此位置:

注意:这只是调式项目仅供学习参考,正式环境下,需要将访问令牌隐式配置到本地,不在其中显示。

3.5 开始启动项目

替换成功后,在DevEco Studio中编译运行此项目,点击右上角绿色运行按钮,启动过程中耐心等待。注意,虚拟机启动需要保证虚拟机连接上网络:

如图所示启动成功:

3.6 项目展示

搜索用户:

下拉实现刷新,下滑会自动刷新

搜索仓库:

下拉实现刷新,下滑会自动刷新

3.7 代码使用示例

使用 RepositoryCard 组件

RepositoryCard(
  repository: gitCodeRepository,
  onTap: () {
    // 处理点击事件
    print('点击了仓库: ${gitCodeRepository.fullName}');
  },
)

使用 UserCard 组件

UserCard(
  user: gitCodeUser,
  onTap: () {
    // 处理点击事件
    print('点击了用户: ${gitCodeUser.name ?? gitCodeUser.login}');
  },
)

跳转到仓库列表页面

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => RepositoryListPage(
      keyword: 'flutter',
      token: 'your_access_token',
    ),
  ),
);

跳转到用户列表页面

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => UserListPage(
      keyword: 'john',
      token: 'your_access_token',
    ),
  ),
);

四、文件结构

新增文件

lib/
├── widgets/
│   ├── repository_card.dart          # 自定义仓库卡片组件
│   └── user_card.dart                # 自定义用户卡片组件
└── pages/
    ├── repository_list_page.dart     # 仓库分页列表页面
    └── user_list_page.dart           # 用户分页列表页面

修改文件

lib/
└── main.dart                          # 主页面(集成新组件)

pubspec.yaml                           # 添加 pull_to_refresh 依赖

完整文件结构

gitcode_pocket_tool/
├── lib/
│   ├── core/
│   │   ├── app_config.dart
│   │   └── gitcode_api.dart
│   ├── pages/
│   │   ├── repository_list_page.dart  # 新增
│   │   └── user_list_page.dart        # 新增
│   ├── widgets/
│   │   ├── repository_card.dart       # 新增
│   │   └── user_card.dart             # 新增
│   └── main.dart                      # 修改
├── pubspec.yaml                       # 修改
└── docs/
    └── 组件封装与分页加载实现文档.md   # 本文档

五、API 说明

GitCodeApiClient.searchUsers

方法签名

Future<List<GitCodeSearchUser>> searchUsers({
  required String keyword,
  required String personalToken,
  int perPage = 10,
  int page = 1,
})

参数说明

  • keyword: 搜索关键字(必需)

  • personalToken: 个人访问令牌(必需)

  • perPage: 每页数据量(默认 10,最大 50)

  • page: 页码(默认 1,最大 100)

返回值

  • List<GitCodeSearchUser>: 用户列表

使用示例

final users = await _client.searchUsers(
  keyword: 'john',
  personalToken: 'your_token',
  perPage: 20,
  page: 1,
);

GitCodeApiClient.searchRepositories

方法签名

Future<List<GitCodeRepository>> searchRepositories({
  required String keyword,
  required String personalToken,
  String? language,
  String? sort,
  String? order,
  int perPage = 10,
  int page = 1,
})

参数说明

  • keyword: 搜索关键字(必需)

  • personalToken: 个人访问令牌(必需)

  • language: 编程语言过滤(可选)

  • sort: 排序方式(可选)

  • order: 排序顺序(可选)

  • perPage: 每页数据量(默认 10,最大 50)

  • page: 页码(默认 1,最大 100)

返回值

  • List<GitCodeRepository>: 仓库列表

使用示例

final repos = await _client.searchRepositories(
  keyword: 'flutter',
  personalToken: 'your_token',
  perPage: 20,
  page: 1,
);

六、最佳实践

1. 组件封装原则

  • 单一职责:每个组件只负责一个功能

  • 可复用性:组件设计时考虑复用场景

  • 参数化:通过参数控制组件行为

  • 文档完善:添加清晰的注释和文档

2. 状态管理

  • 状态集中:相关状态集中管理

  • 状态同步:确保 UI 状态与数据状态同步

  • 错误处理:完善的错误处理和用户提示

3. 性能优化

  • 懒加载:使用 ListView.builder 实现懒加载

  • 分页加载:避免一次性加载大量数据

  • 防重复请求:添加加载状态检查

4. 用户体验

  • 加载提示:清晰的加载状态提示

  • 错误提示:友好的错误信息和重试机制

  • 空状态:优雅的空状态展示

  • 交互反馈:点击、刷新等操作的即时反馈


七、常见问题

Q1: 下拉刷新不生效?

可能原因

  1. RefreshController 未正确初始化

  2. enablePullDown 未设置为 true

  3. 刷新回调方法未正确实现

解决方案

// 确保控制器正确初始化
final RefreshController _refreshController = RefreshController(
  initialRefresh: false,
);
​
// 确保启用下拉刷新
SmartRefresher(
  enablePullDown: true,
  onRefresh: _onRefresh,
  // ...
)
​
// 确保刷新完成后调用
_refreshController.refreshCompleted();

Q2: 上拉加载不触发?

可能原因

  1. enablePullUp 设置为 false

  2. 数据未达到触发加载的阈值

  3. _hasMore 状态不正确

解决方案

// 根据是否有更多数据启用上拉加载
SmartRefresher(
  enablePullUp: _hasMore,
  onLoading: _onLoading,
  // ...
)
​
// 确保正确判断是否还有更多数据
_hasMore = repos.length >= _perPage;

Q3: 分页数据重复?

可能原因

  1. 刷新时未重置页码

  2. 数据追加逻辑错误

解决方案

if (refresh) {
  _currentPage = 1;  // 重置页码
  _repositories.clear();  // 清空数据
  _repositories = repos;  // 替换数据
} else {
  _repositories.addAll(repos);  // 追加数据
}

Q4: 如何自定义卡片样式?

解决方案: 修改 RepositoryCard 组件的样式参数:

Card(
  elevation: 2,  // 修改阴影
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),  // 修改边距
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),  // 修改圆角
  ),
  // ...
)

Q5: 如何修改每页加载数量?

解决方案: 修改 _perPage 常量:

final int _perPage = 20;  // 修改为你需要的数量(最大 50)

八、总结

本次更新成功实现了:

  1. 自定义仓库卡片组件 - 美观、可复用的 UI 组件

  2. 自定义用户卡片组件 - 美观、可复用的 UI 组件

  3. 仓库分页列表页面 - 完整的仓库列表浏览功能

  4. 用户分页列表页面 - 完整的用户列表浏览功能

  5. 下拉刷新功能 - 流畅的刷新体验(支持仓库和用户)

  6. 上拉加载更多 - 自动加载分页数据(支持仓库和用户)

  7. 完整的分页管理 - 可靠的数据加载逻辑

  8. 完善的错误处理 - 友好的用户提示

这些功能大大提升了应用的用户体验,使数据浏览更加高效和流畅。无论是搜索仓库还是搜索用户,都能享受到一致且流畅的浏览体验。


九、参考资料


文档版本: 2.0.0 最后更新: 2024年 维护者: GitCode 口袋工具开发团队


十、更新日志

v2.0.0 (最新)

  • ✨ 新增用户卡片组件 (UserCard)

  • ✨ 新增用户分页列表页面 (UserListPage)

  • ✨ 主页面支持用户搜索和列表跳转

  • 📝 更新文档,添加用户相关功能说明

v1.0.0

  • ✨ 新增仓库卡片组件 (RepositoryCard)

  • ✨ 新增仓库分页列表页面 (RepositoryListPage)

  • ✨ 主页面支持仓库搜索和列表跳转

  • 📝 初始文档版本

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐