【开源鸿蒙跨平台开发--3.1】GitCode口袋工具应用开发教程

第1章:项目初始化与基础架构搭建

因为openharmony编译不是本教程重点,所以本文将不再赘述如何编译openharmony。后续文章亦是。

从零开始构建 GitCode 口袋工具 - 第一步

📚 本章目标

在本章中,你将学习:

  1. 创建 Flutter 项目
  2. 配置项目依赖
  3. 搭建 Material Design 3 主题
  4. 构建底部导航栏框架
  5. 创建首页、搜索页、我的页面基础结构

第一步:创建 Flutter 项目

1.1 初始化项目

打开终端,执行以下命令:

flutter create gitcode_pocket_tool
cd gitcode_pocket_tool

1.2 验证项目

# 检查 Flutter 环境
flutter doctor

# 运行默认应用
flutter run

你会看到 Flutter 默认的计数器应用。


第二步:配置项目依赖

2.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.2 安装依赖

flutter pub get

你会看到所有依赖包被下载安装。


第三步:创建项目目录结构

3.1 创建文件夹

lib/ 目录下创建以下文件夹:

lib/
├── core/              # 核心功能(API、配置)
├── pages/             # 页面
│   └── main_navigation/   # 主导航页面
└── widgets/           # 可复用组件

3.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';
}

说明

  • demoToken:默认为空,用户需要自己输入
  • apiBaseUrl:GitCode API v5 的基础地址

第四步:搭建主应用框架

4.1 修改 main.dart

打开 lib/main.dart完全替换为以下内容:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  
  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});

  
  State<MainNavigationPage> createState() => _MainNavigationPageState();
}

class _MainNavigationPageState extends State<MainNavigationPage> {
  int _currentIndex = 0;
  
  // 三个主页面(稍后创建)
  final List<Widget> _pages = const [
    IntroPage(),      // 首页
    SearchPage(),     // 搜索页
    ProfilePage(),    // 我的页面
  ];

  
  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});

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('首页 - 即将完成'),
      ),
    );
  }
}

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

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('搜索页 - 即将完成'),
      ),
    );
  }
}

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

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('我的页面 - 即将完成'),
      ),
    );
  }
}

4.2 运行查看效果

flutter run

你现在应该看到:

  • ✅ 底部有三个导航按钮(首页、搜索、我的)
  • ✅ 点击可以切换页面
  • ✅ 每个页面显示临时占位文字

第五步:完善首页(IntroPage)

5.1 创建首页文件

创建 lib/pages/main_navigation/intro_page.dart

import 'package:flutter/material.dart';

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

  
  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)),
    );
  }
}

5.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(),    
];

5.3 运行查看

flutter run
# 或者按 'r' 热重载

现在首页应该显示:

  • ✅ 欢迎卡片
  • ✅ 四个功能介绍
  • ✅ 技术栈标签

第六步:完善我的页面(ProfilePage)

6.1 创建我的页面文件

创建 lib/pages/main_navigation/profile_page.dart

import 'package:flutter/material.dart';

class ProfilePage extends StatefulWidget {
  const ProfilePage({super.key});

  
  State<ProfilePage> createState() => _ProfilePageState();
}

class _ProfilePageState extends State<ProfilePage> {
  
  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))),
        ],
      ),
    );
  }
}

6.2 更新 main.dart

lib/main.dart 顶部添加导入:

import 'pages/main_navigation/intro_page.dart';
import 'pages/main_navigation/profile_page.dart';  // 添加这行

删除临时的 ProfilePage 类定义。


第七步:完善搜索页面(SearchPage)

7.1 创建搜索页面文件

创建 lib/pages/main_navigation/search_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});

  
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  final _keywordController = TextEditingController();
  final _tokenController = TextEditingController();
  
  SearchMode _searchMode = SearchMode.user;
  bool _tokenObscured = true;

  
  void dispose() {
    _keywordController.dispose();
    _tokenController.dispose();
    super.dispose();
  }

  
  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'),
      ),
    );
  }
}

7.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';  // 添加这行

删除临时的 SearchPage 类定义。


第八步:测试基础框架

8.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});

  
  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});

  
  State<MainNavigationPage> createState() => _MainNavigationPageState();
}

class _MainNavigationPageState extends State<MainNavigationPage> {
  int _currentIndex = 0;
  
  final List<Widget> _pages = const [
    IntroPage(),
    SearchPage(),
    ProfilePage(),
  ];

  
  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: '我的',
          ),
        ],
      ),
    );
  }
}

8.2 运行并测试

flutter run

测试清单

  • ✅ 点击底部导航栏,三个页面可以正常切换
  • ✅ 首页显示欢迎信息和功能介绍
  • ✅ 搜索页可以切换用户/仓库模式
  • ✅ 搜索页可以显示/隐藏 Token
  • ✅ 我的页面显示个人信息和 Token 配置说明
  • ✅ 整体 UI 使用 Material Design 3 风格

本章总结

🎉 恭喜!你已经完成了第一章的学习。

你学到了什么

  1. 创建 Flutter 项目 - 使用命令行初始化项目
  2. 配置依赖 - 添加 Dio、pull_to_refresh 等包
  3. Material Design 3 - 使用最新的设计系统
  4. 底部导航栏 - 使用 NavigationBar 组件
  5. 页面状态保持 - 使用 IndexedStack
  6. 创建三个主页面 - 首页、搜索页、我的页面
  7. UI 组件 - Card、ListTile、SegmentedButton 等

项目结构

lib/
├── core/
│   └── app_config.dart
├── pages/
│   └── main_navigation/
│       ├── intro_page.dart
│       ├── search_page.dart
│       └── profile_page.dart
└── main.dart

下一章预告

在第二章中,我们将:

  • 🔨 创建 GitCode API 客户端
  • 🔨 实现用户搜索功能
  • 🔨 实现仓库搜索功能
  • 🔨 创建用户列表和仓库列表页面
  • 🔨 实现下拉刷新和上拉加载

准备好了吗?让我们开始第二章的学习吧! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值