| 类型 | 控制方式 | 典型组件 | 复杂度 |
|---|---|---|---|
| 隐式动画 | 自动(改属性即可) | AnimatedContainer/AnimatedOpacity | 低 |
| 显式动画 | 手动(AnimationController) | ScaleTransition/FadeTransition | 中 |
| 自定义动画 | 完全自定义 | CustomPainter+Animation | 高 |
// ✅ 验证通过:Flutter 3.22
class ImplicitDemo extends StatefulWidget {
const ImplicitDemo({super.key});
@override
State<ImplicitDemo> createState() => _ImplicitDemoState();
}
class _ImplicitDemoState extends State<ImplicitDemo> {
bool _expanded = false; Color _color = Colors.blue; double _opacity = 1.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('隐式动画')),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
AnimatedContainer(duration: const Duration(milliseconds: 500), curve: Curves.easeInOutCubic,
width: _expanded ? 250 : 100, height: _expanded ? 250 : 100,
decoration: BoxDecoration(color: _color, borderRadius: BorderRadius.circular(_expanded ? 32 : 8),
boxShadow: [BoxShadow(color: _color.withOpacity(0.4), blurRadius: _expanded ? 20 : 5)]),
child: const Center(child: Text('动画容器', style: TextStyle(color: Colors.white))),
),
const SizedBox(height: 20),
AnimatedOpacity(duration: const Duration(milliseconds: 300), opacity: _opacity, child: const Text('淡入淡出文字', style: TextStyle(fontSize: 20))),
const SizedBox(height: 20),
AnimatedCrossFade(firstChild: const Icon(Icons.wb_sunny, size: 64, color: Colors.orange), secondChild: const Icon(Icons.nightlight, size: 64, color: Colors.indigo), crossFadeState: _expanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 500)),
const SizedBox(height: 20),
AnimatedSwitcher(duration: const Duration(milliseconds: 300), child: Text(_expanded ? '展开状态' : '收起状态', key: ValueKey(_expanded), style: const TextStyle(fontSize: 18))),
const SizedBox(height: 30),
Wrap(spacing: 8, children: [
ElevatedButton(onPressed: () => setState(() => _expanded = !_expanded), child: const Text('切换大小')),
ElevatedButton(onPressed: () => setState(() => _color = _color == Colors.blue ? Colors.purple : Colors.blue), child: const Text('切换颜色')),
ElevatedButton(onPressed: () => setState(() => _opacity = _opacity == 1.0 ? 0.2 : 1.0), child: const Text('切换透明度')),
]),
])),
);
}
}
/// 显式动画——AnimationController
class ExplicitDemo extends StatefulWidget {
const ExplicitDemo({super.key});
@override
State<ExplicitDemo> createState() => _ExplicitDemoState();
}
class _ExplicitDemoState extends State<ExplicitDemo> with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
@override
void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 2)); }
@override
void dispose() { _ctrl.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('显式动画')),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
ScaleTransition(scale: CurvedAnimation(parent: _ctrl, curve: Curves.elasticInOut), child: Container(width: 120, height: 120, decoration: const BoxDecoration(gradient: LinearGradient(colors: [Colors.purple, Colors.blue]), shape: BoxShape.circle), child: const Center(child: Text('缩放', style: TextStyle(color: Colors.white))))),
const SizedBox(height: 40),
RotationTransition(turns: _ctrl, child: const Icon(Icons.refresh, size: 64)),
const SizedBox(height: 40),
SlideTransition(position: Tween<Offset>(begin: const Offset(-1, 0), end: Offset.zero).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic)), child: const Card(child: Padding(padding: EdgeInsets.all(16), child: Text('滑动动画')))),
const SizedBox(height: 30),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ElevatedButton(onPressed: () => _ctrl.forward(), child: const Text('播放')),
ElevatedButton(onPressed: () => _ctrl.reverse(), child: const Text('反向')),
ElevatedButton(onPressed: () => _ctrl.repeat(reverse: true), child: const Text('循环')),
ElevatedButton(onPressed: () => _ctrl.stop(), child: const Text('停止')),
ElevatedButton(onPressed: () => _ctrl.reset(), child: const Text('重置')),
]),
])),
);
}
}
/// 交错动画
class StaggeredDemo extends StatefulWidget {
const StaggeredDemo({super.key});
@override
State<StaggeredDemo> createState() => _StaggeredDemoState();
}
class _StaggeredDemoState extends State<StaggeredDemo> with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
@override
void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 3)); }
@override
void dispose() { _ctrl.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
var slide = Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(CurvedAnimation(parent: _ctrl, curve: const Interval(0.0, 0.4)));
var fade = CurvedAnimation(parent: _ctrl, curve: const Interval(0.2, 0.6));
var scale = Tween<double>(begin: 0.8, end: 1.0).animate(CurvedAnimation(parent: _ctrl, curve: const Interval(0.3, 0.7)));
var color = ColorTween(begin: Colors.blue, end: Colors.purple).animate(CurvedAnimation(parent: _ctrl, curve: const Interval(0.5, 1.0)));
return Scaffold(
appBar: AppBar(title: const Text('交错动画')),
body: Center(child: SlideTransition(position: slide, child: FadeTransition(opacity: fade, child: ScaleTransition(scale: scale, child: AnimatedBuilder(animation: color, builder: (_, child) => Container(width: 200, height: 200, decoration: BoxDecoration(color: color.value, borderRadius: BorderRadius.circular(24)), child: child), child: const Center(child: Text('交错', style: TextStyle(color: Colors.white, fontSize: 24)))))))),
floatingActionButton: FloatingActionButton(onPressed: () => _ctrl.isCompleted ? _ctrl.reverse() : _ctrl.forward(), child: const Icon(Icons.play_arrow)),
);
}
}
/// Curves曲线参考:
/// Curves.linear — 线性
/// Curves.easeIn — 慢启动
/// Curves.easeOut — 慢结束
/// Curves.easeInOut — 慢启动慢结束
/// Curves.elasticInOut — 弹性
/// Curves.bounceInOut — 弹跳
/// Curves.decelerate — 减速
/// Curves.fastOutSlowIn — 快出慢入(Material标准)
// ✅ 验证通过:Flutter 3.22
/// 1. Widget调试技巧
// 在任意位置查看Widget树
debugDumpApp(); // 打印Widget树
debugDumpRenderTree(); // 打印Render树
debugDumpLayerTree(); // 打印Layer树
// 性能叠加层
// MaterialApp(showPerformanceOverlay: true)
// 检查不必要的重建
// 在State.build中添加:
@override
Widget build(BuildContext context) {
// debugPrint('$runtimeType rebuild'); // 查看重建频率
return Container();
}
/// 2. 布局调试
// 添加debugPaintSizeEnabled查看布局边界
// PaintingBinding.instance.debugPaintSizeEnabled = true;
// 使用Flutter Inspector(VS Code/Android Studio内置)
// 查看Widget属性、布局约束、渲染性能
/// 3. 常见错误排查
// RenderFlex overflowed — 子组件超出父容器
// 解决:Expanded/flexible/SingleChildScrollView
// Looking up a deactivated widget's ancestor
// 解决:async回调中检查mounted
// setState() called after dispose()
// 解决:在setState前检查mounted
/// 4. 性能分析
// flutter run --profile
// 使用DevTools的Performance面板
// 使用Timeline查看帧耗时
/// 5. 测试基础
// Unit Test
test('Counter increment', () {
var counter = CounterModel();
counter.increment();
expect(counter.count, 1);
});
// Widget Test
testWidgets('Counter UI', (tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
| 工具 | 用途 | 安装 |
|---|---|---|
| flutter_lints | 代码规范检查 | dev_dependencies |
| build_runner | 代码生成 | dev_dependencies |
| freezed | 不可变数据类 | dependencies |
| json_serializable | JSON序列化 | dependencies |
| go_router | 声明式路由 | dependencies |
| dio | HTTP客户端 | dependencies |
| cached_network_image | 图片缓存 | dependencies |
| flutter_secure_storage | 安全存储 | dependencies |
| shared_preferences | 轻量存储 | dependencies |
| sqflite | SQLite数据库 | dependencies |
| provider | 状态管理 | dependencies |
// ✅ 验证通过:Flutter 3.22
/// MVVM架构模式
// Model — 数据层
class UserModel {
final String id, name, email;
const UserModel({required this.id, required this.name, required this.email});
factory UserModel.fromJson(Map json) => UserModel(id: json['id'], name: json['name'], email: json['email']);
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}
// ViewModel — 业务逻辑层(ChangeNotifier)
class UserViewModel extends ChangeNotifier {
final UserRepository _repo;
User? _user; bool _isLoading = false; String? _error;
User? get user => _user; bool get isLoading => _isLoading; String? get error => _error;
UserViewModel(this._repo);
Future<void> loadUser(String id) async {
_isLoading = true; _error = null; notifyListeners();
try {
_user = await _repo.getUser(id);
_isLoading = false;
} catch (e) {
_error = e.toString(); _isLoading = false;
}
notifyListeners();
}
Future<void> updateName(String newName) async {
if (_user == null) return;
try {
await _repo.updateUser(_user!.id, {'name': newName});
_user = UserModel(id: _user!.id, name: newName, email: _user!.email);
notifyListeners();
} catch (e) {
_error = e.toString(); notifyListeners();
}
}
}
// View — UI层
class UserProfilePage extends StatelessWidget {
const UserProfilePage({super.key});
@override
Widget build(BuildContext context) {
var vm = context.watch<UserViewModel>();
if (vm.isLoading) return const Center(child: CircularProgressIndicator());
if (vm.error != null) return Center(child: Text('Error: ${vm.error}'));
return Column(children: [
Text(vm.user?.name ?? 'Unknown'),
ElevatedButton(onPressed: () => vm.updateName('New Name'), child: const Text('Update')),
]);
}
}
/// Repository模式——隔离数据源
abstract class UserRepository {
Future<User> getUser(String id);
Future<void> updateUser(String id, Map data);
}
class ApiUserRepository implements UserRepository {
final ApiClient _api;
ApiUserRepository(this._api);
@override Future<User> getUser(String id) async { var r = await _api._dio.get('/users/$id'); return User.fromJson(r.data); }
@override Future<void> updateUser(String id, Map data) async { await _api._dio.put('/users/$id', data: data); }
}
class MockUserRepository implements UserRepository {
@override Future<User> getUser(String id) async { await Future.delayed(const Duration(seconds: 1)); return const User(id: '1', name: 'Mock', email: 'mock@test.com'); }
@override Future<void> updateUser(String id, Map data) async { await Future.delayed(const Duration(milliseconds: 500)); }
}
// 推荐项目结构:
// lib/
// ├── main.dart — 入口
// ├── app.dart — App配置
// ├── core/ — 核心工具
// │ ├── theme.dart
// │ ├── constants.dart
// │ ├── extensions.dart
// │ └── utils.dart
// ├── models/ — 数据模型
// │ ├── user.dart
// │ └── product.dart
// ├── repositories/ — 数据仓库
// │ ├── user_repository.dart
// │ └── product_repository.dart
// ├── providers/ — 状态管理
// │ ├── auth_provider.dart
// │ └── cart_provider.dart
// ├── screens/ — 页面
// │ ├── home/
// │ │ ├── home_screen.dart
// │ │ └── widgets/ — 页面私有Widget
// │ ├── profile/
// │ └── settings/
// ├── widgets/ — 共享Widget
// │ ├── app_button.dart
// │ ├── app_card.dart
// │ └── loading_indicator.dart
// └── services/ — 外部服务
// ├── api_client.dart
// ├── storage_service.dart
// └── analytics_service.dart
3个圆点依次弹跳的加载动画。
自定义Toggle开关,带滑动+颜色变化流畅动画。
ListView每个item依次从左滑入并淡入,用Interval交错。
动画导演——掌握Flutter动画体系
下一课:Hero动画——继续深入学习Flutter开发。