【新手学习笔记Day10】DevEco Studio GitCode口袋工具:毛玻璃二次元主题搭建指南
2. 毛玻璃核心:通过 BackdropFilter + ImageFilter.blur(sigmaX:10, sigmaY:10) 实现模糊效果,配合半透明白卡片( Color.fromARGB(200, 255, 255, 255) ),强化玻璃质感。GitCode口袋工具是基于Flutter开发、适配DevEco Studio的轻量化GitCode平台客户端,核心亮点的是毛玻璃二次元视觉风
前言:本文章是我在看到其他人的文章后的学习笔记,我参照的文章链接在下面:
一、项目核心定位
GitCode口袋工具是基于Flutter开发、适配DevEco Studio的轻量化GitCode平台客户端,核心亮点的是毛玻璃二次元视觉风格,功能覆盖登录验证、动态查看、仓库管理、个人信息展示,整体架构模块化,便于维护与扩展。
二、前期准备
-
开发环境配置 - 开发工具:DevEco Studio(需适配Flutter开发插件) - 技术依赖:在项目 pubspec.yaml 中添加核心依赖,支撑网络请求、本地存储等功能: yaml dependencies: flutter: sdk: flutter dio: ^5.7.0 # 网络请求核心库 shared_preferences: ^2.3.0 # 本地存储(令牌管理) flutter_easyloading: ^3.0.5 # 加载状态提示(可选优化) - 权限配置:确保项目开启网络权限,满足GitCode API请求需求。
-
项目结构规划
采用模块化拆分思路,明确各目录职责,降低代码耦合度,结构如下:
plaintext
lib/
├── main.dart # 应用入口,初始化主题与根组件
├── constants/ # 常量集中管理(API地址、存储键等)
└── app_constants.dart # 核心常量定义
├── themes/ # 主题配置专属目录
└── app_theme.dart # 毛玻璃二次元主题核心配置
├── services/ # 网络服务层
└── gitcode_service.dart # GitCode API请求封装(单例模式)
├── screens/ # 页面组件目录(核心功能页)
├── login_screen.dart # 令牌登录页
├── main_screen.dart # 底部导航主页面
├── home_screen.dart # 首页动态展示
├── repo_screen.dart # 热门/个人仓库页
└── profile_screen.dart # 个人中心页
└── widgets/ # 自定义复用组件
├── glass_card.dart # 毛玻璃卡片(全局复用核心组件)
└── activity_card.dart # 动态列表卡片组件
三、核心模块实现步骤
-
常量定义(constants/app_constants.dart) 集中管理API基础地址、令牌存储键、历史记录限制等常量,避免硬编码,便于统一修改:
class AppConstants { // GitCode API基础地址 static const String apiBaseUrl = 'https://api.gitcode.com/api/v5'; // 当前令牌本地存储键 static const String tokenStorageKey = 'gitcode_token'; // 历史令牌存储键(新增) static const String tokenHistoryKey = 'gitcode_token_history'; // 历史令牌最大存储数量(新增) static const int maxHistoryCount = 5; } -
毛玻璃二次元主题核心配置(themes/app_theme.dart) 主题是风格落地核心,统一管控颜色、字体、组件样式,重点实现二次元视觉与毛玻璃适配:
import 'package:flutter/material.dart'; // SPDX-License-Identifier: Apache-2.0 import 'package:flutter/material.dart'; /// 毛玻璃二次元主题配置 class AppTheme { /// 字体大小:超大 static const double fontSizeXXLarge = 24; /// 字体大小:大 static const double fontSizeXLarge = 18; /// 字体大小:中 static const double fontSizeLarge = 16; /// 字体大小:中 static const double fontSizeMedium = 14; /// 字体大小:小 static const double fontSizeSmall = 12; /// 主色调(二次元粉紫色) static const Color primaryColor = Color(0xFF8B5CF6); /// 次要色调(天空蓝) static const Color secondaryColor = Color(0xFF38BDF8); /// 强调色(樱花粉) static const Color accentColor = Color(0xFFEC4899); /// 背景色(浅紫灰) static const Color backgroundColor = Color(0xFFF5F3FF); /// 卡片背景色(半透明白) static const Color cardBackgroundColor = Color(0xC8FFFFFF); /// 文字主色 static const Color primaryTextColor = Color(0xFF1E1B4B); /// 文字次要色 static const Color secondaryTextColor = Color(0xFF64748B); /// 边框颜色 static const Color borderColor = Color.fromARGB(50, 139, 92, 246); /// 阴影效果 static const BoxShadow cardShadow = BoxShadow( color: Color.fromRGBO(139, 92, 246, 0.1), // 主题色阴影 blurRadius: 12, // 模糊半径 offset: Offset(0, 4), // 偏移量 ); /// 圆角大小 static const BorderRadius cardBorderRadius = BorderRadius.all(Radius.circular(16)); /// 构建Material主题 static ThemeData get themeData => ThemeData( // 主色调 primaryColor: primaryColor, // 画布背景色 scaffoldBackgroundColor: backgroundColor, // 卡片主题 cardTheme: CardTheme( color: cardBackgroundColor, elevation: 0, shape: RoundedRectangleBorder(borderRadius: cardBorderRadius), shadowColor: cardShadow.color, ), // AppBar主题 appBarTheme: const AppBarTheme( backgroundColor: Colors.transparent, // 透明背景(配合毛玻璃) foregroundColor: primaryTextColor, // 文字颜色 elevation: 0, // 无阴影 centerTitle: true, // 标题居中 ), // 底部导航栏主题 bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: cardBackgroundColor, // 半透明背景 selectedItemColor: primaryColor, // 选中颜色 unselectedItemColor: secondaryTextColor, // 未选中颜色 elevation: 8, // 阴影 type: BottomNavigationBarType.fixed, // 固定类型 ), // 文字主题 textTheme: const TextTheme( bodyLarge: TextStyle(color: primaryTextColor), bodyMedium: TextStyle(color: secondaryTextColor), titleLarge: TextStyle( fontWeight: FontWeight.bold, color: primaryTextColor, ), ), // 按钮主题 elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: primaryColor, // 按钮背景色 foregroundColor: Colors.white, // 按钮文字色 shape: RoundedRectangleBorder(borderRadius: cardBorderRadius), // 圆角 padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), // 内边距 ), ), // 输入框主题 inputDecorationTheme: InputDecorationTheme( filled: true, // 填充背景 fillColor: cardBackgroundColor, // 填充色 border: OutlineInputBorder( borderRadius: cardBorderRadius, // 圆角 borderSide: BorderSide(color: borderColor), // 边框色 ), enabledBorder: OutlineInputBorder( borderRadius: cardBorderRadius, borderSide: BorderSide(color: borderColor), ), focusedBorder: OutlineInputBorder( borderRadius: cardBorderRadius, borderSide: BorderSide(color: primaryColor, width: 2), // 聚焦边框色 ), ), ); } -
毛玻璃卡片组件封装(widgets/glass_card.dart) 全局复用核心组件,通过 BackdropFilter + ImageFilter.blur 实现毛玻璃效果,适配各页面:
// SPDX-License-Identifier: Apache-2.0 import 'package:flutter/material.dart'; import 'dart:ui'; // 用于ImageFilter(毛玻璃) import '../themes/app_theme.dart'; /// 毛玻璃卡片组件 class GlassCard extends StatelessWidget { /// 卡片子组件 final Widget child; /// 内边距 final EdgeInsets padding; /// 外边距 final EdgeInsets margin; const GlassCard({ Key? key, required this.child, this.padding = const EdgeInsets.all(16), this.margin = const EdgeInsets.only(bottom: 16), }) : super(key: key); @override Widget build(BuildContext context) { return Container( margin: margin, //毛玻璃效果核心:BackdropFilter + ImageFilter.blur child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), // 模糊程度 child: Container( padding: padding, decoration: BoxDecoration( color: AppTheme.cardBackgroundColor, // 半透明背景 borderRadius: AppTheme.cardBorderRadius, // 圆角 boxShadow: [AppTheme.cardShadow], // 阴影 border: Border.all( color: Colors.white.withOpacity(0.2), // 白色描边(增强玻璃感) width: 1, ), ), child: child, ), ), ); } } -
网络服务层封装(services/gitcode_service.dart) 采用单例模式封装GitCode API请求,统一处理令牌添加、错误拦截,支撑核心功能:
// SPDX-License-Identifier: Apache-2.0 import 'package:dio/dio.dart'; import '../constants/app_constants.dart'; /// GitCode API服务类 class GitCodeService { // 单例模式:确保全局只有一个实例 static final GitCodeService _instance = GitCodeService._internal(); factory GitCodeService() => _instance; late Dio dio; // Dio实例,用于网络请求 // 内部构造函数 GitCodeService._internal() { // 初始化Dio dio = Dio( BaseOptions( baseUrl: AppConstants.apiBaseUrl, // API基础地址 connectTimeout: const Duration(seconds: 15), // 连接超时 receiveTimeout: const Duration(seconds: 15), // 接收超时 responseType: ResponseType.json, // 响应类型 contentType: 'application/json; charset=utf-8', // 请求类型 headers: { 'Accept': 'application/json', // 接受JSON格式 }, ), ); // 添加拦截器(日志、错误处理) dio.interceptors.add( InterceptorsWrapper( // 请求拦截 onRequest: (options, handler) { // 如果有令牌,添加到请求头 String? token = _accessToken; if (token != null && token.isNotEmpty) { options.headers['Authorization'] = 'Bearer $token'; } print('🚀 请求: ${options.method} ${options.uri}'); handler.next(options); // 继续请求 }, // 响应拦截 onResponse: (response, handler) { print('✅ 响应: ${response.statusCode} ${response.requestOptions.uri}'); handler.next(response); // 继续处理响应 }, // 错误拦截 onError: (DioException e, handler) { print('❌ 错误: ${e.message}'); print('❌ 响应状态: ${e.response?.statusCode}'); print('❌ 响应体: ${e.response?.data}'); // 统一错误信息 String errorMsg = '网络请求失败'; if (e.response?.statusCode == 404) { errorMsg = 'API端点不存在'; } else if (e.response?.statusCode == 401) { errorMsg = '无效的访问令牌'; } else if (e.type == DioExceptionType.connectionTimeout) { errorMsg = '网络超时,请检查网络连接'; } // 包装错误 e = DioException( requestOptions: e.requestOptions, type: e.type, error: errorMsg, response: e.response, ); handler.next(e); // 继续处理错误 }, ), ); } // 令牌存储(全局变量,实际项目建议使用本地存储) static String? _accessToken; /// 设置访问令牌 void setToken(String token) { _accessToken = token; } /// 清除访问令牌 void clearToken() { _accessToken = null; } /// 获取当前用户信息 Future<Map<String, dynamic>> fetchUserInfo() async { try { final response = await dio.get('/user'); // 请求用户信息 return response.data as Map<String, dynamic>; // 返回数据 } on DioException catch (e) { throw Exception(e.error ?? '获取用户信息失败'); // 抛出异常 } } /// 获取用户仓库列表 Future<List<dynamic>> fetchMyRepositories() async { try { final response = await dio.get( '/user/repos', queryParameters: { 'visibility': 'all', // 所有可见性 'affiliation': 'owner', // 仅自己的仓库 'page': 1, // 页码 'per_page': 20, // 每页数量 }, ); return response.data as List<dynamic>; // 返回仓库列表 } on DioException catch (e) { throw Exception(e.error ?? '获取我的仓库失败'); // 抛出异常 } } /// 获取动态列表 Future<List<dynamic>> fetchActivities() async { try { final response = await dio.get( '/events', queryParameters: {'page': 1, 'per_page': 20}, // 分页参数 ); return response.data as List<dynamic>; // 返回动态列表 } catch (e) { print('⚠️ 动态API调用失败,返回模拟数据: $e'); // 返回模拟数据(防止API不可用导致崩溃) return [ { "id": 1, "type": "PushEvent", "actor": {"login": "user1", "avatar_url": "https://placehold.co/40x40/8B5CF6/ffffff?text=U1"}, "repo": {"name": "user1/repo1"}, "created_at": DateTime.now().subtract(const Duration(hours: 1)).toIso8601String() }, { "id": 2, "type": "WatchEvent", "actor": {"login": "user2", "avatar_url": "https://placehold.co/40x40/38BDF8/ffffff?text=U2"}, "repo": {"name": "user2/repo2"}, "created_at": DateTime.now().subtract(const Duration(hours: 2)).toIso8601String() }, ]; } } /// 获取热门仓库 Future<List<dynamic>> fetchHotRepositories() async { try { final response = await dio.get( '/search/repositories', queryParameters: { 'q': 'stars:>100', // 搜索条件:星数>100 'sort': 'stars', // 按星数排序 'order': 'desc', // 降序 'page': 1, // 页码 'per_page': 10, // 每页数量 }, ); final data = response.data as Map<String, dynamic>; return data['items'] as List<dynamic>; // 返回热门仓库列表 } catch (e) { print('⚠️ 热门仓库API调用失败,返回模拟数据: $e'); // 返回模拟数据 return [ { "id": 101, "name": "flutter-kit", "full_name": "google/flutter-kit", "description": "Flutter 开发工具包", "language": "Dart", "stargazers_count": 5000, "forks_count": 1200, "html_url": "https://gitcode.com/google/flutter-kit" }, ]; } } } -
核心页面实现(screens目录) (1)登录页(login_screen.dart) 令牌验证入口,采用毛玻璃卡片承载表单,适配主题风格,验证通过跳转主页面:
// SPDX-License-Identifier: Apache-2.0 import 'package:flutter/material.dart'; import '../services/gitcode_service.dart'; import '../themes/app_theme.dart'; import '../widgets/glass_card.dart'; import './main_screen.dart'; /// 登录页面 class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @override State<LoginScreen> createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { /// 令牌输入控制器 final TextEditingController _tokenController = TextEditingController(); /// 加载状态 bool _isLoading = false; /// 错误信息 String? _errorMessage; /// 登录处理函数 void _handleLogin() async { setState(() { _isLoading = true; // 显示加载中 _errorMessage = null; // 清空错误信息 }); final token = _tokenController.text.trim(); // 获取输入的令牌 if (token.isEmpty) { setState(() { _isLoading = false; _errorMessage = '请输入访问令牌'; // 空令牌提示 }); return; } try { // 初始化服务并设置令牌 final service = GitCodeService(); service.setToken(token); // 验证令牌是否有效(调用用户信息API) await service.fetchUserInfo(); // 登录成功,跳转到主页面 if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const MainScreen()), ); } } catch (e) { setState(() { _isLoading = false; _errorMessage = e.toString(); // 显示错误信息 }); } } @override Widget build(BuildContext context) { return Scaffold( // 背景渐变(增强二次元风格) body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFF5F3FF), // 浅紫灰 Color(0xFFE0E7FF), // 浅蓝紫 ], ), ), child: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ // Logo区域 GlassCard( margin: const EdgeInsets.only(bottom: 32), padding: const EdgeInsets.all(24), child: Column( children: [ // Logo图标 Container( width: 100, height: 100, decoration: BoxDecoration( color: AppTheme.primaryColor, borderRadius: BorderRadius.circular(20), boxShadow: [AppTheme.cardShadow], ), child: const Icon( Icons.code, size: 60, color: Colors.white, ), ), const SizedBox(height: 16), // 应用标题 const Text( 'GitCode 客户端', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: AppTheme.primaryTextColor, ), ), const SizedBox(height: 8), // 副标题 const Text( '使用访问令牌登录', style: TextStyle( fontSize: 16, color: AppTheme.secondaryTextColor, ), ), ], ), ), // 登录表单 GlassCard( child: Column( children: [ // 令牌输入框 TextField( controller: _tokenController, obscureText: true, // 密码模式(隐藏令牌) decoration: InputDecoration( labelText: '访问令牌', hintText: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', hintStyle: const TextStyle(color: AppTheme.secondaryTextColor), errorText: _errorMessage, // 错误提示 prefixIcon: const Icon(Icons.security, color: AppTheme.primaryColor), suffixIcon: IconButton( icon: const Icon(Icons.clear, color: AppTheme.secondaryTextColor), onPressed: () => _tokenController.clear(), // 清空输入 ), ), maxLines: 1, textInputAction: TextInputAction.done, onSubmitted: (_) => _handleLogin(), // 回车登录 ), const SizedBox(height: 16), // 帮助链接 Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { // 打开获取令牌的帮助页面(预留) print('打开获取令牌帮助'); }, child: const Text( '如何获取令牌?', style: TextStyle(color: AppTheme.primaryColor), ), ), ), const SizedBox(height: 24), // 登录按钮 SizedBox( width: double.infinity, height: 50, child: ElevatedButton.icon( onPressed: _isLoading ? null : _handleLogin, // 加载中禁用 icon: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), ) : const Icon(Icons.login), label: const Text( '登录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ), ], ), ), ], ), ), ), ), ), ); } }(2)主页面(main_screen.dart) 底部导航核心页,切换首页、仓库、个人中心,导航栏适配毛玻璃效果:
import 'dart:ui'; import 'package:flutter/material.dart'; import './home_screen.dart'; import './repo_screen.dart'; import './profile_screen.dart'; import '../themes/app_theme.dart'; class MainScreen extends StatefulWidget { const MainScreen({Key? key}) : super(key: key); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { int _selectedIndex = 0; // 当前选中页面索引 static const List<Widget> _widgetOptions = [HomeScreen(), RepoScreen(), ProfileScreen()]; // 页面列表 // 导航栏点击切换 void _onItemTapped(int index) => setState(() => _selectedIndex = index); @override Widget build(BuildContext context) { return Scaffold( // 背景渐变(与登录页一致,风格统一) body: Container( decoration: const BoxDecoration( gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFF5F3FF), Color(0xFFE0E7FF)]), ), child: _widgetOptions.elementAt(_selectedIndex), ), // 毛玻璃底部导航栏 bottomNavigationBar: Container( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: BottomNavigationBar( items: const [ BottomNavigationBarItem(icon: Icon(Icons.home_outlined), activeIcon: Icon(Icons.home), label: '首页'), BottomNavigationBarItem(icon: Icon(Icons.code_outlined), activeIcon: Icon(Icons.code), label: '仓库'), BottomNavigationBarItem(icon: Icon(Icons.person_outlined), activeIcon: Icon(Icons.person), label: '我的'), ], currentIndex: _selectedIndex, onTap: _onItemTapped, backgroundColor: AppTheme.cardBackgroundColor, selectedItemColor: AppTheme.primaryColor, unselectedItemColor: AppTheme.secondaryTextColor, elevation: 8, type: BottomNavigationBarType.fixed, ), ), ), ); } }(3)首页动态/仓库/个人中心页 均基于 GlassCard 组件复用毛玻璃风格,核心逻辑为:请求接口数据→展示加载/错误/空状态→渲染列表/详情,以个人中心为例(展示用户信息+登出功能):
import './login_screen.dart'; import 'package:flutter/material.dart'; import '../services/gitcode_service.dart'; import '../themes/app_theme.dart'; import '../widgets/glass_card.dart'; class ProfileScreen extends StatefulWidget { const ProfileScreen({Key? key}) : super(key: key); @override State<ProfileScreen> createState() => _ProfileScreenState(); } class _ProfileScreenState extends State<ProfileScreen> { final GitCodeService _service = GitCodeService(); Map<String, dynamic>? _userInfo; // 用户信息 bool _isLoading = true; // 加载状态 String? _errorMessage; // 错误提示 @override void initState() {super.initState(); _fetchUserInfo();} // 获取用户信息 Future<void> _fetchUserInfo() async { setState(() {_isLoading = true; _errorMessage = null;}); try { final userInfo = await _service.fetchUserInfo(); setState(() => _userInfo = userInfo); } catch (e) { setState(() => _errorMessage = e.toString()); } finally { setState(() => _isLoading = false); } } // 格式化日期(YYYY年MM月DD日) String _formatDate(String? dateStr) => dateStr == null ? '' : DateTime.parse(dateStr).toString().split(' ')[0].replaceAll('-', '年') + '日'; // 构建统计项(仓库/关注者/关注中) Widget _buildStatItem(String label, int count, IconData icon) => Column( children: [ Container(width: 56, height: 56, decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), color: AppTheme.primaryColor.withOpacity(0.1)), child: Icon(icon, size: 28, color: AppTheme.primaryColor)), const SizedBox(height: 8), Text(count.toString(), style: const TextStyle(fontSize: 24, fontWeight: bold, color: AppTheme.primaryTextColor)), const SizedBox(height: 4), Text(label, style: TextStyle(fontSize: AppTheme.fontSizeSmall, color: AppTheme.secondaryTextColor)), ], ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('我的'), backgroundColor: Colors.transparent, actions: [IconButton(icon: const Icon(Icons.settings_outlined), onPressed: () => print('打开设置'))]), body: RefreshIndicator( onRefresh: _fetchUserInfo, // 下拉刷新 color: AppTheme.primaryColor, backgroundColor: AppTheme.cardBackgroundColor, child: _isLoading ? const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [CircularProgressIndicator(color: AppTheme.primaryColor), SizedBox(height: 16), Text('加载中...')])) : _errorMessage != null ? Center( child: GlassCard( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), Text(_errorMessage!, textAlign: TextAlign.center, style: const TextStyle(color: AppTheme.secondaryTextColor)), const SizedBox(height: 16), ElevatedButton(onPressed: () {_service.clearToken(); Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen()));}, child: const Text('重新登录')), ]), ), ) : _userInfo == null ? const Center(child: Text('未能获取用户信息')) : SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 用户信息卡片 GlassCard( margin: const EdgeInsets.only(bottom: 24), padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ CircleAvatar(radius: 60, backgroundImage: NetworkImage(_userInfo!['avatar_url'] ?? ''), backgroundColor: AppTheme.secondaryColor.withOpacity(0.1)), const SizedBox(height: 16), Text(_userInfo!['name'] ?? _userInfo!['login'] ?? '匿名用户', style: const TextStyle(fontSize: AppTheme.fontSizeXXLarge, fontWeight: bold, color: AppTheme.primaryTextColor)), Text('@${_userInfo!['login']}', style: TextStyle(fontSize: AppTheme.fontSizeLarge, color: AppTheme.secondaryTextColor)), const SizedBox(height: 16), if (_userInfo!['bio'] != null) Text(_userInfo!['bio'] ?? '', textAlign: TextAlign.center, style: TextStyle(fontSize: AppTheme.fontSizeMedium, color: AppTheme.secondaryTextColor)), ], ), ), // 统计卡片 GlassCard( margin: const EdgeInsets.only(bottom: 24), padding: const EdgeInsets.all(24), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem('仓库', _userInfo!['public_repos'] ?? 0, Icons.code_outlined), _buildStatItem('关注者', _userInfo!['followers'] ?? 0, Icons.people_outlined), _buildStatItem('关注中', _userInfo!['following'] ?? 0, Icons.person_add_outlined), ]), ), // 登出按钮(红色主题,区分常规按钮) SizedBox( width: double.infinity, height: 50, child: ElevatedButton.icon( onPressed: () {_service.clearToken(); Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen()));}, icon: const Icon(Icons.logout), label: const Text('登出'), style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent, foregroundColor: Colors.white), ), ), ], ), ), ), ); } } -
应用入口(main.dart)
初始化应用,绑定主题配置,设置初始页面为登录页:
// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
import './screens/login_screen.dart';
import './themes/app_theme.dart';
/// 应用入口函数
void main() {
runApp(const MyApp()); // 启动应用
}
/// 根组件
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GitCode 客户端', // 应用标题
theme: AppTheme.themeData, // 应用主题
home: const LoginScreen(), // 初始页面(登录页)
debugShowCheckedModeBanner: false, // 隐藏调试横幅
);
}
}
四、毛玻璃二次元风格核心要点
1. 背景基础:全局使用 LinearGradient 浅紫灰→浅蓝紫渐变,奠定柔和二次元基调,避免纯色单调。
2. 毛玻璃核心:通过 BackdropFilter + ImageFilter.blur(sigmaX:10, sigmaY:10) 实现模糊效果,配合半透明白卡片( Color.fromARGB(200, 255, 255, 255) ),强化玻璃质感。
3. 视觉细节:
- 圆角:全局统一16px圆角,弱化尖锐感,契合二次元柔和风格;
- 配色:以粉紫为主色,天蓝、樱花粉为辅,文字区分主次色,视觉统一;
- 阴影:使用主题色淡阴影( opacity:0.1 ),增加组件层次感,不突兀。
4. 组件复用:封装 GlassCard 组件,全局页面复用,确保风格一致性,减少重复代码。
五、运行测试
1. 依赖安装:配置 pubspec.yaml 后,点击DevEco Studio「Pub get」安装依赖;
2. 设备适配:选择模拟器或真实设备,确保设备网络正常;
3. 功能测试:验证登录(令牌有效性)、动态加载、仓库切换、个人信息展示、登出等核心功能,检查毛玻璃效果适配性。
更多推荐



所有评论(0)