Flutter快速构建Gitcode口袋工具指南
需要注意鸿蒙系统的“签名”!
本文适合想快速构建Gitcode 口袋工具的flutter开发者,本人学习的教程:
同时对于实践过程中遇到的问题做一个总结与细节补充
一.第一步创建flutter项目
1.初始化项目
打开终端(win+R),输入cmd,执行以下命令:
| 1. | flutter create gitcode_pocket_tool |
| 2. | cd gitcode_pocket_tool |
结果如图所示:

在 DevEco Studio 中打开 ohos 模块
1. 打开 DevEco Studio
2. File → Open
3.找到你的项目路径:gitcode_pocket_tool/ohos/
4.选择 ohos 文件夹并打开
2.验证项目
(1)检查flutter环境
输入flutter doctor
(2)运行默认应用
输入flutter code
结果如图所示:
图(1)
图(2)
【注】配置签名(解决图2的错误)
1. 在 DevEco Studio 中打开 ohos 项目后
2. File → Project Structure
3. 选择 Signing Configs 标签页
4. 勾选 Automatically generate signature
5. 点击 Apply → OK
配置签名后会看到 Flutter 默认的计数器应用。

第二步:配置项目依赖
1 编辑 pubspec.yaml
打开 pubspec.yaml 文件,修改为以下内容:
name: gitcode_pocket_tool
description: "GitCode 口袋工具 - 一个轻量级的 GitCode 客户端"
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.6.2 <4.0.0'
dependencies:
flutter:
sdk: flutter
# 网络请求
dio: ^5.7.0
# 下拉刷新 & 上拉加载
pull_to_refresh: ^2.0.0
# URL 启动器
url_launcher: ^6.3.1
# 路由管理
go_router: ^14.6.2
# 图标
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
# 添加资源文件(我们后续会用到)
assets:
- assets/images/
打开文件后出现:
全部删除并改为 上述代码:(如下图)
2.安装依赖
打开终端输入:
flutter pub get
可得到:
你会看到所有依赖包被下载安装。
第三步 :创建项目目录结构
1 创建文件夹
在 lib/ 目录下创建以下文件夹:
lib/
├── core/ # 核心功能(API、配置)
├── pages/ # 页面
│ └── main_navigation/ # 主导航页面
└── widgets/ # 可复用组件
2 创建配置文件
创建 lib/core/app_config.dart:
/// 应用配置
class AppConfig {
/// 默认的演示 Token(用户可以在"我的"页面修改)
static const String demoToken = '';
/// GitCode API 基础地址
static const String apiBaseUrl = 'https://api.gitcode.com/api/v5';
}
如图:

第四步:搭建主应用框架
1. 修改 main.dart
打开 lib/main.dart,完全替换为以下内容:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GitCode 口袋工具',
debugShowCheckedModeBanner: false,
// Material Design 3 主题
theme: ThemeData(
colorSchemeSeed: Colors.indigo, // 使用靛蓝色作为主色调
useMaterial3: true,
visualDensity: VisualDensity.standard,
),
home: const MainNavigationPage(),
);
}
}
/// 主导航页面(底部导航栏)
class MainNavigationPage extends StatefulWidget {
const MainNavigationPage({super.key});
@override
State<MainNavigationPage> createState() => _MainNavigationPageState();
}
class _MainNavigationPageState extends State<MainNavigationPage> {
int _currentIndex = 0;
// 三个主页面(稍后创建)
final List<Widget> _pages = const [
IntroPage(), // 首页
SearchPage(), // 搜索页
ProfilePage(), // 我的页面
];
@override
Widget build(BuildContext context) {
return Scaffold(
// 使用 IndexedStack 保持页面状态
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
// Material Design 3 底部导航栏
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: '首页',
),
NavigationDestination(
icon: Icon(Icons.search_outlined),
selectedIcon: Icon(Icons.search),
label: '搜索',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: '我的',
),
],
),
);
}
}
// 临时占位页面(稍后会替换)
class IntroPage extends StatelessWidget {
const IntroPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('首页 - 即将完成'),
),
);
}
}
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('搜索页 - 即将完成'),
),
);
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('我的页面 - 即将完成'),
),
);
}
}
如图:

2 .运行查看效果
输入:
flutter run
效果:
✅ 底部有三个导航按钮(首页、搜索、我的)
✅ 点击可以切换页面
✅ 每个页面显示临时占位文字
第五步:完善首页(IntroPage)
1 创建首页文件
创建 lib/pages/main_navigation/intro_page.dart:
import 'package:flutter/material.dart';
class IntroPage extends StatelessWidget {
const IntroPage({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('GitCode 口袋工具'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
// 欢迎区域
_buildWelcomeSection(theme),
const SizedBox(height: 32),
// 功能介绍
_buildFeaturesSection(theme),
const SizedBox(height: 32),
// 技术栈
_buildTechStackSection(theme),
],
),
),
);
}
/// 欢迎区域
Widget _buildWelcomeSection(ThemeData theme) {
return Card(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Icon(
Icons.code,
size: 80,
color: theme.colorScheme.primary,
),
const SizedBox(height: 16),
Text(
'欢迎使用 GitCode 口袋工具',
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'轻量级的 GitCode 客户端',
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
),
),
);
}
/// 功能介绍
Widget _buildFeaturesSection(ThemeData theme) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'核心功能',
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 16),
_buildFeatureCard(
icon: Icons.search,
title: '搜索用户和仓库',
description: '快速搜索 GitCode 上的用户和代码仓库',
color: Colors.blue,
),
const SizedBox(height: 12),
_buildFeatureCard(
icon: Icons.folder_open,
title: '浏览仓库文件',
description: '查看仓库的文件和目录结构',
color: Colors.orange,
),
const SizedBox(height: 12),
_buildFeatureCard(
icon: Icons.event,
title: '追踪仓库动态',
description: '实时查看仓库的最新动态和事件',
color: Colors.green,
),
const SizedBox(height: 12),
_buildFeatureCard(
icon: Icons.people,
title: '贡献者统计',
description: '分析仓库贡献者及其统计数据',
color: Colors.purple,
),
],
);
}
/// 单个功能卡片
Widget _buildFeatureCard({
required IconData icon,
required String title,
required String description,
required Color color,
}) {
return Card(
child: ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color),
),
title: Text(title),
subtitle: Text(description),
),
);
}
/// 技术栈
Widget _buildTechStackSection(ThemeData theme) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'技术栈',
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildTechChip('Flutter 3.6.2+', Colors.blue),
_buildTechChip('Dart 3.6.2+', Colors.blue[700]!),
_buildTechChip('Material Design 3', Colors.indigo),
_buildTechChip('Dio 5.7.0', Colors.green),
_buildTechChip('GitCode API v5', Colors.orange),
],
),
),
),
],
);
}
/// 技术标签
Widget _buildTechChip(String label, Color color) {
return Chip(
label: Text(label),
backgroundColor: color.withOpacity(0.1),
labelStyle: TextStyle(color: color),
side: BorderSide(color: color.withOpacity(0.3)),
);
}
}
2 更新 main.dart
修改 lib/main.dart,在文件顶部添加导入:
import 'package:flutter/material.dart';
import 'pages/main_navigation/intro_page.dart'; // 添加这行
void main() {
runApp(const MyApp());
}
// ... 其余代码保持不变,删除之前的临时 IntroPage 类
修改 _pages 列表(删除之前的临时 IntroPage 定义):
final List<Widget> _pages = const [
IntroPage(), // 使用新的首页
SearchPage(),
ProfilePage(),
];
3 运行查看
flutter run
# 或者按 'r' 热重载
现在首页应该显示:
✅ 欢迎卡片
✅ 四个功能介绍
✅ 技术栈标签
第六步:完善我的页面(ProfilePage)
1 创建我的页面文件
创建 lib/pages/main_navigation/profile_page.dart:
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('我的'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const SizedBox(height: 20),
// 头像和昵称
_buildAvatarSection(theme),
const SizedBox(height: 32),
// 个人信息卡片
_buildInfoSection(theme),
const SizedBox(height: 16),
// Token 配置卡片
_buildTokenSection(theme),
],
),
),
);
}
/// 头像区域
Widget _buildAvatarSection(ThemeData theme) {
return Column(
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: theme.colorScheme.primary.withOpacity(0.3),
width: 3,
),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: ClipOval(
child: Container(
color: theme.colorScheme.primaryContainer,
child: Icon(
Icons.person,
size: 60,
color: theme.colorScheme.onPrimaryContainer,
),
),
),
),
const SizedBox(height: 16),
Text(
'GitCode 用户',
style: theme.textTheme.headlineSmall,
),
const SizedBox(height: 4),
Text(
'请配置 Access Token 以使用完整功能',
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
],
);
}
/// 个人信息
Widget _buildInfoSection(ThemeData theme) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'关于应用',
style: theme.textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
_buildInfoRow('应用名称', 'GitCode 口袋工具'),
_buildInfoRow('版本号', '1.0.0'),
_buildInfoRow('开发者', 'Flutter Developer'),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(color: Colors.grey)),
Text(value, style: const TextStyle(fontWeight: FontWeight.w500)),
],
),
);
}
/// Token 配置
Widget _buildTokenSection(ThemeData theme) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.key,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'Access Token 配置',
style: theme.textTheme.titleMedium,
),
],
),
const SizedBox(height: 16),
Text(
'要使用搜索功能,你需要在 GitCode 获取 Access Token:',
style: theme.textTheme.bodySmall,
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep('1. 登录 https://gitcode.com'),
_buildStep('2. 进入设置 → 访问令牌'),
_buildStep('3. 创建新令牌并复制'),
_buildStep('4. 在搜索页面输入令牌'),
],
),
),
],
),
),
);
}
Widget _buildStep(String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Icon(Icons.check_circle_outline, size: 16),
const SizedBox(width: 8),
Expanded(child: Text(text, style: const TextStyle(fontSize: 12))),
],
),
);
}
}
2 更新 main.dart
在 lib/main.dart 顶部添加导入:
import 'pages/main_navigation/intro_page.dart';
import 'pages/main_navigation/profile_page.dart'; // 添加这行
删除临时的 ProfilePage 类定义。
第七步:完善搜索页面(SearchPage)
1.创建搜索页面分析
创建lib/pages/main_navigation/sear ch_page.dart:
import 'package:flutter/material.dart';
/// 搜索模式枚举
enum SearchMode {
user('用户'),
repo('仓库');
const SearchMode(this.label);
final String label;
}
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
final _keywordController = TextEditingController();
final _tokenController = TextEditingController();
SearchMode _searchMode = SearchMode.user;
bool _tokenObscured = true;
@override
void dispose() {
_keywordController.dispose();
_tokenController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 搜索类型切换
_buildSearchModeSelector(theme),
const SizedBox(height: 24),
// 搜索输入框
_buildSearchInput(theme),
const SizedBox(height: 16),
// Token 输入框
_buildTokenInput(theme),
const SizedBox(height: 24),
// 搜索按钮
_buildSearchButton(theme),
const SizedBox(height: 32),
// 使用提示
_buildUsageTips(theme),
],
),
),
);
}
/// 搜索模式选择器
Widget _buildSearchModeSelector(ThemeData theme) {
return SegmentedButton<SearchMode>(
segments: const [
ButtonSegment(
value: SearchMode.user,
label: Text('用户'),
icon: Icon(Icons.person),
),
ButtonSegment(
value: SearchMode.repo,
label: Text('仓库'),
icon: Icon(Icons.folder),
),
],
selected: {_searchMode},
onSelectionChanged: (Set<SearchMode> newSelection) {
setState(() {
_searchMode = newSelection.first;
});
},
);
}
/// 搜索输入框
Widget _buildSearchInput(ThemeData theme) {
return TextField(
controller: _keywordController,
decoration: InputDecoration(
labelText: '搜索关键字',
hintText: _searchMode == SearchMode.user
? '输入用户名或昵称'
: '输入仓库名称',
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
onSubmitted: (_) => _performSearch(),
);
}
/// Token 输入框
Widget _buildTokenInput(ThemeData theme) {
return TextField(
controller: _tokenController,
obscureText: _tokenObscured,
decoration: InputDecoration(
labelText: 'Access Token',
hintText: '输入你的 GitCode Access Token',
prefixIcon: const Icon(Icons.key),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_tokenObscured
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
onPressed: () {
setState(() {
_tokenObscured = !_tokenObscured;
});
},
),
),
);
}
/// 搜索按钮
Widget _buildSearchButton(ThemeData theme) {
return FilledButton.icon(
onPressed: _performSearch,
icon: const Icon(Icons.search),
label: const Text('开始搜索'),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
);
}
/// 使用提示
Widget _buildUsageTips(ThemeData theme) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.lightbulb_outline,
color: theme.colorScheme.primary,
size: 20,
),
const SizedBox(width: 8),
Text(
'使用提示',
style: theme.textTheme.titleMedium,
),
],
),
const SizedBox(height: 12),
_buildTipItem('💡 首次使用需要输入 Access Token'),
_buildTipItem('💡 Token 会临时保存,无需重复输入'),
_buildTipItem('💡 搜索用户可以使用昵称或登录名'),
_buildTipItem('💡 点击搜索结果可查看详细信息'),
],
),
),
);
}
Widget _buildTipItem(String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text(
text,
style: const TextStyle(fontSize: 14, height: 1.5),
),
);
}
/// 执行搜索
void _performSearch() {
final keyword = _keywordController.text.trim();
final token = _tokenController.text.trim();
// 输入验证
if (keyword.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入搜索关键字')),
);
return;
}
if (token.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入 Access Token')),
);
return;
}
// TODO: 下一章会实现实际的搜索功能
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('准备搜索${_searchMode.label}: $keyword'),
),
);
}
}
2.更新main.dart
在lib/main.dart顶部添加导入
import 'pages/main_navigation/intro_page.dart';
import 'pages/main_navigation/profile_page.dart';
import 'pages/main_navigation/search_page.dart'; // 添加这行
删除临时的Sear chPage类定义。
第八步:测试基础框架
1.完整的main.dart
你的lib/main.dart现在应该是这样的:
import 'package:flutter/material.dart';
import 'pages/main_navigation/intro_page.dart';
import 'pages/main_navigation/search_page.dart';
import 'pages/main_navigation/profile_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GitCode 口袋工具',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
visualDensity: VisualDensity.standard,
),
home: const MainNavigationPage(),
);
}
}
class MainNavigationPage extends StatefulWidget {
const MainNavigationPage({super.key});
@override
State<MainNavigationPage> createState() => _MainNavigationPageState();
}
class _MainNavigationPageState extends State<MainNavigationPage> {
int _currentIndex = 0;
final List<Widget> _pages = const [
IntroPage(),
SearchPage(),
ProfilePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: '首页',
),
NavigationDestination(
icon: Icon(Icons.search_outlined),
selectedIcon: Icon(Icons.search),
label: '搜索',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: '我的',
),
],
),
);
}
}
2.运行并测试
flutter run
结果:

✅ 点击底部导航栏,三个页面可以正常切换
✅ 首页显示欢迎信息和功能介绍
✅ 搜索页可以切换用户/仓库模式
✅ 搜索页可以显示/隐藏 Token
✅ 我的页面显示个人信息和 Token 配置说明
✅ 整体 UI 使用 Material Design 3 风格
总结:
需要注意鸿蒙系统的“签名”!
更多推荐



所有评论(0)