本章从架构设计模式出发,涵盖大型项目结构、多端适配、插件生态、无障碍与可访问性、代码规范与质量保障,是 Flutter 工程化的综合实践指南。
📋 章节目录
| 节 | 主题 | 核心内容 |
|---|---|---|
| 12.1 | 架构设计模式 | MVC、MVVM、Clean Architecture |
| 12.2 | 大型项目结构实践 | 分层架构、依赖注入、Either 模式 |
| 12.3 | 多端统一与适配 | 响应式布局、平台适配、导航适配 |
| 12.4 | 插件生态与工具链 | 常用库精选、build_runner、Melos |
| 12.5 | 无障碍与可访问性 | Semantics、对比度、字体缩放 |
| 12.6 | 代码规范与质量保障 | analysis_options、pre-commit、质量门禁 |
12.1 架构设计模式
良好的架构设计是大型 Flutter 项目可维护性的保证。本节介绍三种主流架构模式及其在 Flutter 中的实践。
一、MVC(Model-View-Controller)
Model:数据层(User、Product 等数据类)
View:Widget 层(UI 展示)
Controller:逻辑层(处理用户输入、调用 Model)
dart
// Model
class UserModel {
final String name;
final String email;
const UserModel({required this.name, required this.email});
}
// Controller
class UserController extends GetxController {
final Rx<UserModel?> user = Rx<UserModel?>(null);
final RxBool isLoading = false.obs;
Future<void> loadUser(int id) async {
isLoading.value = true;
try {
user.value = await UserRepository.findById(id);
} finally {
isLoading.value = false;
}
}
}
// View(Widget)
class UserView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = Get.find<UserController>();
return Obx(() =>
controller.isLoading.value
? const CircularProgressIndicator()
: UserCard(user: controller.user.value!),
);
}
}
二、MVVM(Model-View-ViewModel)
Model:数据与业务规则
ViewModel:持有 UI 状态,处理逻辑
View:观察 ViewModel 状态,自动更新
dart
// ViewModel(暴露 UI 状态)
class ProductListViewModel extends ChangeNotifier {
List<Product> _products = [];
bool _isLoading = false;
String? _errorMessage;
String _searchQuery = '';
List<Product> get products => _filteredProducts;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
bool get isEmpty => _products.isEmpty && !_isLoading;
List<Product> get _filteredProducts => _searchQuery.isEmpty
? _products
: _products.where((p) => p.name.contains(_searchQuery)).toList();
Future<void> loadProducts() async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
_products = await ProductRepository.fetchAll();
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
void search(String query) {
_searchQuery = query;
notifyListeners();
}
}
// View(只关心 UI 展示)
class ProductListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ProductListViewModel()..loadProducts(),
child: Consumer<ProductListViewModel>(
builder: (context, vm, _) {
if (vm.isLoading) return const LoadingWidget();
if (vm.errorMessage != null) return ErrorWidget(message: vm.errorMessage!);
if (vm.isEmpty) return const EmptyState();
return ProductGrid(products: vm.products);
},
),
);
}
}
三、Clean Architecture(推荐大型项目)
┌────────────────────────────────────────┐
│ Presentation Layer(UI + ViewModel) │
│ Widget / Page / Controller / Provider │
├────────────────────────────────────────┤
│ Domain Layer(业务规则) │
│ Entity / UseCase / Repository接口 │
├────────────────────────────────────────┤
│ Data Layer(数据来源) │
│ Repository实现 / DataSource / Model │
└────────────────────────────────────────┘
dart
// =============== Domain Layer ===============
// Entity(纯业务对象)
class Product {
final int id;
final String name;
final double price;
const Product({required this.id, required this.name, required this.price});
}
// Repository 接口(抽象,不关心实现)
abstract class ProductRepository {
Future<List<Product>> getProducts({String? category, int page = 1});
Future<Product> getProductById(int id);
Future<void> toggleFavorite(int productId);
}
// UseCase(单一业务用例,可独立测试)
class GetProductsUseCase {
final ProductRepository repository;
GetProductsUseCase(this.repository);
Future<List<Product>> call({String? category, int page = 1}) async {
final products = await repository.getProducts(category: category, page: page);
return products.where((p) => p.isAvailable).toList();
}
}
// =============== Data Layer ===============
class ProductDto {
final int id;
final String name;
final double price;
final bool isActive;
const ProductDto({required this.id, required this.name, required this.price, required this.isActive});
factory ProductDto.fromJson(Map<String, dynamic> json) => ProductDto(
id: json['id'],
name: json['name'],
price: json['price'].toDouble(),
isActive: json['is_active'],
);
Product toDomain() => Product(id: id, name: name, price: price);
}
class ProductRepositoryImpl implements ProductRepository {
final ProductRemoteDataSource _remote;
final ProductLocalDataSource _local;
ProductRepositoryImpl(this._remote, this._local);
@override
Future<List<Product>> getProducts({String? category, int page = 1}) async {
try {
final dtos = await _remote.fetchProducts(category: category, page: page);
await _local.cacheProducts(dtos);
return dtos.map((dto) => dto.toDomain()).toList();
} catch (_) {
final cached = await _local.getCachedProducts();
return cached.map((dto) => dto.toDomain()).toList();
}
}
}
// =============== Presentation Layer ===============
@riverpod
Future<List<Product>> productList(ProductListRef ref, {String? category}) async {
final useCase = ref.watch(getProductsUseCaseProvider);
return useCase(category: category);
}
class ProductListPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(productListProvider());
return productsAsync.when(
loading: () => const ProductGridSkeleton(),
error: (e, _) => ErrorView(error: e),
data: (products) => ProductGrid(products: products),
);
}
}
小结
| 架构 | 适用规模 | 特点 |
|---|---|---|
| MVC | 小型项目 | 简单,快速上手,适合 GetX |
| MVVM | 中型项目 | ViewModel 与 View 解耦,可测试 |
| Clean Architecture | 大型项目 | 分层清晰,高可测试性,团队协作友好 |
12.2 大型项目结构实践
大型 Flutter 项目需要清晰的分层架构、完善的依赖注入体系和统一的编码规范,才能支撑多人长期协作。
一、分层架构(data / domain / ui)
lib/
├── core/ # 核心基础设施(跨所有功能共享)
│ ├── network/
│ │ ├── api_client.dart
│ │ ├── interceptors/
│ │ └── exceptions/
│ ├── storage/
│ ├── utils/
│ ├── extensions/
│ └── constants/
│
├── features/ # 功能模块(按业务域划分)
│ ├── auth/
│ │ ├── data/
│ │ │ ├── datasources/
│ │ │ │ ├── auth_remote_datasource.dart
│ │ │ │ └── auth_local_datasource.dart
│ │ │ ├── models/
│ │ │ │ └── user_dto.dart
│ │ │ └── repositories/
│ │ │ └── auth_repository_impl.dart
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ └── user.dart
│ │ │ ├── repositories/
│ │ │ │ └── auth_repository.dart
│ │ │ └── usecases/
│ │ │ ├── login_usecase.dart
│ │ │ └── logout_usecase.dart
│ │ └── presentation/
│ │ ├── providers/
│ │ │ └── auth_provider.dart
│ │ ├── pages/
│ │ │ └── login_page.dart
│ │ └── widgets/
│ │ └── login_form.dart
│ │
│ ├── product/
│ └── order/
│
├── shared/ # 跨功能共享组件
│ ├── widgets/
│ ├── theme/
│ └── router/
│
└── main.dart
二、依赖注入与全局单例
2.1 Riverpod + 分层 Provider
dart
// core/network/providers.dart
@riverpod
DioClient dioClient(DioClientRef ref) => DioClient();
// features/auth/data/providers.dart
@riverpod
AuthRemoteDataSource authRemoteDataSource(AuthRemoteDataSourceRef ref) {
return AuthRemoteDataSourceImpl(ref.watch(dioClientProvider));
}
@riverpod
AuthRepository authRepository(AuthRepositoryRef ref) {
return AuthRepositoryImpl(
remote: ref.watch(authRemoteDataSourceProvider),
local: ref.watch(authLocalDataSourceProvider),
);
}
// features/auth/domain/providers.dart
@riverpod
LoginUseCase loginUseCase(LoginUseCaseRef ref) {
return LoginUseCase(ref.watch(authRepositoryProvider));
}
2.2 GetIt(服务定位器)
dart
final getIt = GetIt.instance;
Future<void> setupDependencies() async {
getIt.registerSingleton<DioClient>(DioClient());
getIt.registerSingleton<DatabaseService>(await DatabaseService.init());
getIt.registerLazySingleton<ProductRemoteDataSource>(
() => ProductRemoteDataSourceImpl(getIt<DioClient>()),
);
getIt.registerLazySingleton<ProductRepository>(
() => ProductRepositoryImpl(
remote: getIt<ProductRemoteDataSource>(),
local: getIt<ProductLocalDataSource>(),
),
);
getIt.registerFactory<GetProductsUseCase>(
() => GetProductsUseCase(getIt<ProductRepository>()),
);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await setupDependencies();
runApp(const MyApp());
}
三、统一的响应/错误处理
dart
import 'package:fpdart/fpdart.dart';
abstract class Failure {
final String message;
const Failure(this.message);
}
class NetworkFailure extends Failure {
const NetworkFailure(super.message);
}
class CacheFailure extends Failure {
const CacheFailure(super.message);
}
// Repository 返回 Either<Failure, T>
abstract class ProductRepository {
Future<Either<Failure, List<Product>>> getProducts();
}
// UI 处理
final result = await useCase();
result.fold(
(failure) => showError(failure.message), // Left = 失败
(products) => showProducts(products), // Right = 成功
);
四、编码规范
dart
// ✅ 文件命名:snake_case
// product_detail_page.dart
// ✅ 类命名:PascalCase
class ProductDetailPage {}
// ✅ 变量/方法命名:camelCase
final currentUser = UserModel();
Future<void> loadUserData() async {}
// ✅ 常量:lowerCamelCase
const maxRetryCount = 3;
const String apiBaseUrl = 'https://api.example.com';
// ✅ 私有成员:下划线前缀
final _controller = TextEditingController();
void _handleSubmit() {}
// ✅ Barrel exports
// features/product/product.dart
export 'presentation/pages/product_list_page.dart';
export 'presentation/pages/product_detail_page.dart';
export 'domain/entities/product.dart';
小结
| 实践 | 要点 |
|---|---|
| 分层 | data / domain / presentation 三层分离 |
| 功能模块 | 按业务域组织,每个模块独立完整 |
| 依赖注入 | Riverpod/GetIt 管理依赖,避免全局单例硬编码 |
| Either 模式 | 函数式错误处理,避免 try-catch 满天飞 |
| 命名规范 | 统一 snake_case 文件名,PascalCase 类名 |
12.3 多端统一与适配
Flutter 的跨端能力是其核心优势。合理的设计能让同一套代码在手机、平板、桌面、Web 上都有良好的体验。
一、响应式布局与屏幕适配
1.1 flutter_screenutil
yaml
dependencies:
flutter_screenutil: ^5.9.0
dart
void main() {
runApp(
ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸(以 iPhone X 为基准)
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) => MaterialApp(
theme: AppTheme.lightTheme,
home: child,
routes: routes,
),
child: const HomePage(),
),
);
}
Widget build(BuildContext context) {
return Container(
width: 100.w, // 自动适配宽度
height: 200.h, // 自动适配高度
padding: EdgeInsets.all(16.r),
child: Text(
'标题',
style: TextStyle(fontSize: 18.sp),
),
);
}
1.2 自定义响应式断点
dart
class Breakpoints {
static const double compact = 600;
static const double medium = 1200;
static bool isCompact(BuildContext context) =>
MediaQuery.of(context).size.width < compact;
static bool isMedium(BuildContext context) {
final w = MediaQuery.of(context).size.width;
return w >= compact && w < medium;
}
static bool isExpanded(BuildContext context) =>
MediaQuery.of(context).size.width >= medium;
}
class ResponsiveLayout extends StatelessWidget {
final Widget compact;
final Widget? medium;
final Widget? expanded;
const ResponsiveLayout({
super.key,
required this.compact,
this.medium,
this.expanded,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= Breakpoints.medium) {
return expanded ?? medium ?? compact;
}
if (constraints.maxWidth >= Breakpoints.compact) {
return medium ?? compact;
}
return compact;
},
);
}
}
// 使用
ResponsiveLayout(
compact: const MobileLayout(),
medium: const TabletLayout(),
expanded: const DesktopLayout(),
)
二、平台自适应组件
dart
class AdaptiveSwitch extends StatelessWidget {
final bool value;
final ValueChanged<bool>? onChanged;
const AdaptiveSwitch({super.key, required this.value, this.onChanged});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return switch (theme.platform) {
TargetPlatform.iOS || TargetPlatform.macOS =>
CupertinoSwitch(value: value, onChanged: onChanged),
_ => Switch(value: value, onChanged: onChanged),
};
}
}
三、多语言与多主题支持
dart
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeProvider);
final themeMode = ref.watch(themeModeProvider);
return MaterialApp.router(
routerConfig: router,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeMode,
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
debugShowCheckedModeBanner: false,
);
}
}
四、不同屏幕尺寸的导航适配
dart
class AdaptiveNavigationScaffold extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
final Widget body;
const AdaptiveNavigationScaffold({
super.key,
required this.selectedIndex,
required this.onDestinationSelected,
required this.body,
});
static const destinations = [
NavigationDestination(icon: Icon(Icons.home), label: '首页'),
NavigationDestination(icon: Icon(Icons.explore), label: '发现'),
NavigationDestination(icon: Icon(Icons.person), label: '我的'),
];
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1200) {
// 桌面:永久侧边抽屉
return Scaffold(
body: Row(
children: [
NavigationDrawer(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
children: destinations
.map((d) => NavigationDrawerDestination(
icon: d.icon,
label: Text(d.label),
))
.toList(),
),
Expanded(child: body),
],
),
);
} else if (constraints.maxWidth >= 600) {
// 平板:侧边 NavigationRail
return Scaffold(
body: Row(
children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
labelType: NavigationRailLabelType.all,
destinations: destinations
.map((d) => NavigationRailDestination(
icon: d.icon,
label: Text(d.label),
))
.toList(),
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: body),
],
),
);
} else {
// 手机:底部导航栏
return Scaffold(
body: body,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
),
);
}
},
);
}
}
小结
| 适配维度 | 工具/方案 |
|---|---|
| 尺寸自适应 | flutter_screenutil、LayoutBuilder |
| 响应式布局 | 断点系统、ResponsiveLayout 组件 |
| 平台风格 | AdaptiveSwitch、Platform.isIOS |
| 导航适配 | 手机→底部栏,平板→Rail,桌面→Drawer |
| 多主题 | ThemeMode、ThemeExtension |
| 多语言 | flutter_localizations、动态切换 |
12.4 插件生态与工具链
Flutter 拥有活跃的插件生态。了解常用第三方库和开发工具链,能大幅提升开发效率。
一、常用第三方库精选
1.1 网络与数据
| 库 | 功能 | 推荐度 |
|---|---|---|
dio |
HTTP 客户端,拦截器,文件上传 | ⭐⭐⭐⭐⭐ |
retrofit |
基于 Dio 的类型安全 REST 客户端 | ⭐⭐⭐⭐ |
json_serializable |
JSON 代码生成 | ⭐⭐⭐⭐⭐ |
freezed |
不可变数据类 + Union 类型 | ⭐⭐⭐⭐⭐ |
1.2 状态管理
| 库 | 功能 | 推荐度 |
|---|---|---|
flutter_riverpod |
现代状态管理(推荐) | ⭐⭐⭐⭐⭐ |
provider |
官方推荐,轻量 | ⭐⭐⭐⭐ |
flutter_bloc |
事件驱动,大型项目 | ⭐⭐⭐⭐ |
get |
全功能一体化框架 | ⭐⭐⭐ |
1.3 路由
| 库 | 功能 | 推荐度 |
|---|---|---|
go_router |
官方团队,声明式路由 | ⭐⭐⭐⭐⭐ |
auto_route |
代码生成,类型安全 | ⭐⭐⭐⭐ |
1.4 本地存储
| 库 | 功能 | 推荐度 |
|---|---|---|
shared_preferences |
键值对存储 | ⭐⭐⭐⭐⭐ |
hive_flutter |
高性能 NoSQL | ⭐⭐⭐⭐ |
isar |
现代 NoSQL + 全文搜索 | ⭐⭐⭐⭐ |
sqflite |
SQLite 关系型数据库 | ⭐⭐⭐ |
1.5 UI 与媒体
| 库 | 功能 | 推荐度 |
|---|---|---|
cached_network_image |
网络图片缓存 | ⭐⭐⭐⭐⭐ |
flutter_svg |
SVG 渲染 | ⭐⭐⭐⭐⭐ |
lottie |
Lottie 动画 | ⭐⭐⭐⭐⭐ |
rive |
Rive 交互动画 | ⭐⭐⭐⭐ |
flutter_screenutil |
屏幕尺寸适配 | ⭐⭐⭐⭐ |
shimmer |
骨架屏占位动画 | ⭐⭐⭐⭐ |
1.6 工具与调试
| 库 | 功能 | 推荐度 |
|---|---|---|
logger |
分级日志输出 | ⭐⭐⭐⭐⭐ |
sentry_flutter |
错误上报 | ⭐⭐⭐⭐⭐ |
permission_handler |
权限申请 | ⭐⭐⭐⭐⭐ |
url_launcher |
打开 URL/邮件/电话 | ⭐⭐⭐⭐⭐ |
package_info_plus |
获取 App 版本信息 | ⭐⭐⭐⭐ |
device_info_plus |
获取设备信息 | ⭐⭐⭐⭐ |
二、代码生成工具
bash
# 一次性生成
dart run build_runner build --delete-conflicting-outputs
# 监听文件变化,自动重新生成(开发时推荐)
dart run build_runner watch --delete-conflicting-outputs
触发代码生成的场景:
json_serializable→ 生成*.g.dartfreezed→ 生成*.freezed.dart+*.g.dartriverpod_generator→ 生成*.g.dartgo_router_builder→ 生成路由代码mockito→ 生成*.mocks.dart
makefile
# Makefile
.PHONY: gen clean test build
gen:
dart run build_runner build --delete-conflicting-outputs
gen-watch:
dart run build_runner watch --delete-conflicting-outputs
clean:
flutter clean && flutter pub get
test:
flutter test --coverage
format:
dart format . --line-length=100
lint:
flutter analyze
build-android:
flutter build appbundle --release --dart-define-from-file=.env.prod
build-ios:
flutter build ipa --release --dart-define-from-file=.env.prod
三、Melos 自动化工具
bash
# 安装 Melos
dart pub global activate melos
melos bootstrap # 引导所有包
melos run gen # 代码生成
melos run test # 全量测试
melos run lint # 代码检查
四、IDE 支持
VS Code 推荐设置(settings.json):
json
{
"editor.formatOnSave": true,
"editor.rulers": [100],
"[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.tabSize": 2
},
"dart.lineLength": 100,
"dart.hotReloadOnSave": "always",
"dart.previewFlutterUiGuides": true
}
analysis_options.yaml(代码质量):
yaml
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
missing_required_param: error
missing_return: error
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'
linter:
rules:
- always_declare_return_types
- annotate_overrides
- avoid_empty_else
- avoid_print
- prefer_const_constructors
- prefer_const_declarations
- prefer_final_fields
- sized_box_for_whitespace
- use_key_in_widget_constructors
小结
| 类别 | 推荐栈 |
|---|---|
| HTTP | Dio + Retrofit |
| 状态管理 | Riverpod + Freezed |
| 路由 | GoRouter |
| 本地存储 | SharedPreferences + Isar |
| 代码生成 | build_runner + json_serializable + freezed |
| Monorepo | Melos |
| 错误上报 | Sentry |
| 日志 | Logger |
12.5 无障碍与可访问性(Accessibility)
无障碍设计确保视障、听障和行动不便的用户也能使用你的 App。Flutter 通过 Semantics 树与平台辅助功能(TalkBack / VoiceOver)集成,实现屏幕阅读器支持。
一、Semantics 基础
dart
// 手动添加语义信息
Semantics(
label: '购物车图标,当前有 3 件商品',
button: true,
child: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: _goToCart,
),
)
// 图片需要手动添加语义描述
Image.network(
product.imageUrl,
semanticLabel: '${product.name} 商品图片',
)
// 排除不需要的语义(纯装饰性元素)
ExcludeSemantics(
child: Image.asset('assets/images/decorative_divider.png'),
)
二、常用 Semantics 属性
dart
Semantics(
// 基础标签
label: '提交订单按钮', // 屏幕阅读器朗读的文字
hint: '双击以提交订单', // 操作提示
value: '¥299.00', // 当前值
// 状态
enabled: true,
selected: isActive,
checked: isChecked,
toggled: isOn,
// 角色
button: true,
header: true,
link: true,
image: true,
textField: true,
// 操作
onTap: _handleTap,
onLongPress: _handleLongPress,
child: child,
)
三、常见场景适配
dart
// ❌ GestureDetector 没有语义信息
GestureDetector(
onTap: _addToCart,
child: Container(child: const Text('加入购物车')),
)
// ✅ 添加 Semantics
Semantics(
label: '加入购物车',
button: true,
child: GestureDetector(
onTap: _addToCart,
child: Container(child: const Text('加入购物车')),
),
)
// 列表项
Semantics(
label: '${product.name},价格 ${product.price} 元,${product.inStock ? "有货" : "缺货"}',
child: ProductCard(product: product),
)
// 分组语义(MergeSemantics 合并子节点)
MergeSemantics(
child: ListTile(
leading: const Icon(Icons.notifications),
title: const Text('推送通知'),
trailing: Switch(
value: _pushEnabled,
onChanged: (v) => setState(() => _pushEnabled = v),
),
),
)
// 屏幕阅读器朗读:"推送通知,开关,已开启"
四、对比度与字体缩放
dart
// WCAG AA 标准:文字与背景对比度 ≥ 4.5:1
// ❌ 低对比度
Text('重要提示', style: TextStyle(color: Color(0xFFBBBBBB)))
// ✅ 高对比度
Text('重要提示', style: TextStyle(color: Color(0xFF333333)))
// 支持系统字体缩放
MaterialApp(
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: MediaQuery.of(context).textScaler.clamp(
minScaleFactor: 0.8,
maxScaleFactor: 1.5,
),
),
child: child!,
);
},
)
五、测试无障碍
dart
// 开启语义调试器(开发时使用)
MaterialApp(
showSemanticsDebugger: true,
home: const MyApp(),
)
// Widget 测试中检查语义
testWidgets('产品卡片包含正确的语义标签', (tester) async {
await tester.pumpWidget(
MaterialApp(home: ProductCard(product: testProduct)),
);
expect(
find.bySemanticsLabel('Flutter 入门教程,价格 99 元'),
findsOneWidget,
);
});
六、无障碍检查清单
| 检查项 | 要求 |
|---|---|
| 所有可交互元素有语义标签 | Semantics(label: ...) 或内置 Widget |
| 图片有替代文字 | semanticLabel 或 ExcludeSemantics(装饰图) |
| 足够的颜色对比度 | ≥ 4.5:1(WCAG AA) |
| 支持字体缩放 | 适配 textScaler,保证布局不溢出 |
| 最小触摸区域 48×48 dp | Material 规范最低要求 |
| 焦点顺序合理 | Tab 键遍历顺序自然 |
| 状态变化有反馈 | 切换按钮朗读新状态 |
12.6 代码规范与质量保障
统一的代码规范是团队协作的基础。Flutter 项目通过 analysis_options、pre-commit hooks、代码审查流程和自动化质量门禁,保障代码质量。
一、analysis_options.yaml 完整配置
yaml
include: package:flutter_lints/flutter.yaml
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
missing_required_param: error
missing_return: error
dead_code: warning
unused_import: warning
unnecessary_null_comparison: warning
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'
- 'build/**'
- 'lib/generated/**'
linter:
rules:
# ===== 代码风格 =====
- always_declare_return_types
- annotate_overrides
- prefer_const_constructors
- prefer_const_declarations
- prefer_final_fields
- prefer_final_locals
- sort_child_properties_last
- use_key_in_widget_constructors
# ===== 正确性 =====
- avoid_print
- avoid_empty_else
- cancel_subscriptions
- close_sinks
# ===== 性能 =====
- sized_box_for_whitespace
- avoid_unnecessary_containers
- use_decorated_box
# ===== 可读性 =====
- prefer_single_quotes
- directives_ordering
- require_trailing_commas
二、pre-commit Hooks
yaml
# lefthook.yml
pre-commit:
parallel: true
commands:
dart-format:
glob: "*.dart"
run: dart format --set-exit-if-changed {staged_files}
flutter-analyze:
run: flutter analyze --no-pub
tests:
run: flutter test --no-pub
pre-push:
commands:
full-test:
run: flutter test --coverage
bash
brew install lefthook
lefthook install
lefthook run pre-commit # 手动触发
三、PR 规范
markdown
## PR 标题格式
feat(module): 添加订单列表分页功能
fix(auth): 修复 Token 刷新死循环
refactor(cart): 重构购物车状态管理为 Riverpod
## PR 描述模板(.github/pull_request_template.md)
## 变更说明
<!-- 简述本次变更的内容和目标 -->
## 变更类型
- [ ] 新功能(feat)
- [ ] Bug 修复(fix)
- [ ] 代码重构(refactor)
- [ ] 性能优化(perf)
## 测试
- [ ] 已添加单元测试
- [ ] 已在真机测试(iOS / Android)
- [ ] 已测试暗色模式
## 关联 Issue
Closes #123
四、日志分级规范
dart
final logger = Logger(
printer: kReleaseMode
? ProductionFilter()
: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
printTime: true,
),
level: kReleaseMode ? Level.warning : Level.trace,
);
class ApiClient {
void _logRequest(String url, Map<String, dynamic>? body) {
logger.d('→ $url', extra: body);
}
void _logResponse(String url, int statusCode, dynamic data) {
if (statusCode >= 400) {
logger.w('← $url [$statusCode]', error: data);
} else {
logger.d('← $url [$statusCode]');
}
}
void _logError(String url, Object error, StackTrace stack) {
logger.e('✗ $url', error: error, stackTrace: stack);
}
}
五、Melos 工作区脚本
yaml
# melos.yaml
name: my_flutter_workspace
packages:
- apps/*
- packages/*
scripts:
lint:
exec: flutter analyze
description: 分析所有包
format:
exec: dart format .
description: 格式化所有 Dart 文件
test:
exec: flutter test --coverage
description: 运行所有测试
gen:
exec: dart run build_runner build --delete-conflicting-outputs
description: 运行代码生成
prepare:
run: melos bootstrap && melos run gen
description: 初始化工作区
bash
melos bootstrap # 安装所有包的依赖
melos run lint # 检查所有包
melos run test # 测试所有包
melos run gen # 代码生成(所有包)
六、质量门禁(Quality Gate)
yaml
# .github/workflows/quality_gate.yml
name: Quality Gate
on: [pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.6'
cache: true
- run: flutter pub get
- name: 格式检查
run: dart format --output=none --set-exit-if-changed .
- name: 静态分析
run: flutter analyze --fatal-warnings
- name: 测试与覆盖率
run: |
flutter test --coverage
total=$(lcov --summary coverage/lcov.info 2>&1 | grep "lines" | awk '{print $2}' | tr -d '%')
if (( $(echo "$total < 70" | bc -l) )); then
echo "❌ 测试覆盖率 $total% 低于要求的 70%"
exit 1
fi
echo "✅ 测试覆盖率: $total%"
小结
| 工具/实践 | 目的 |
|---|---|
| analysis_options.yaml | 自动检测代码问题和风格 |
| pre-commit hooks | 提交前强制检查 |
| PR 规范 | 统一 PR 格式,提高审查效率 |
| 日志分级 | 生产环境不输出调试日志 |
| Melos 脚本 | 统一工作区命令 |
| 质量门禁 | CI 自动化验证质量标准 |
章节总结
| 知识点 | 必掌握程度 |
|---|---|
| Clean Architecture | ⭐⭐⭐⭐⭐ |
| 分层目录结构 | ⭐⭐⭐⭐⭐ |
| Riverpod / GetIt 依赖注入 | ⭐⭐⭐⭐⭐ |
| 响应式布局断点系统 | ⭐⭐⭐⭐ |
| 导航适配(手机/平板/桌面) | ⭐⭐⭐⭐ |
| 常用插件技术栈 | ⭐⭐⭐⭐⭐ |
| Semantics 无障碍 | ⭐⭐⭐ |
| 质量门禁 + CI 流程 | ⭐⭐⭐⭐⭐ |
🎉 恭喜!全部 12 章 Flutter 知识体系文档已完整整合。
从基础入门(第 1 章)到进阶原理(第 11 章),再到实战最佳实践(第 12 章),
构建了完整的 Flutter 学习路线图。
👉 返回 README 目录 查看完整文档导航