本系列第一部分的反响令人难以置信。许多读者在尝试了一些方法后,分享了他们的想法、经验和问题。但还有更多值得探索的地方。
在过去的一段时间里,我继续分析了 Google 最复杂的 Flutter 实现------从 Material 3 动画到 Flutter Gallery 的性能优化。我发现的模式挑战了常见的 Flutter 实践。
最令人惊讶的发现是? Google 的高级技术往往比你在流行教程中找到的解决方案更简单。
让我向你展示我学到的确切内容。
自定义绘制(Custom Painters):考虑Layers,而非Path
社区通常的做法:
dart
class ComplexChartPainter extends CustomPainter {
final List<DataPoint> data;
final Animation<double> animation;
ComplexChartPainter({
required this.data,
required this.animation,
});
@override
void paint(Canvas canvas, Size size) {
final path = Path();
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
for (int i = 0; i < data.length - 1; i++) {
final current = data[i];
final next = data[i + 1];
final deltaX = next.x - current.x;
final controlPoint1 = Offset(
current.x + deltaX * 0.3,
current.y,
);
final controlPoint2 = Offset(
current.x + deltaX * 0.7,
next.y,
);
path.cubicTo(
controlPoint1.dx, controlPoint1.dy,
controlPoint2.dx, controlPoint2.dy,
next.x, next.y,
);
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Google的方式:
dart
class ChartPainter extends CustomPainter {
const ChartPainter({
required this.data,
required this.animation,
});
final List<DataPoint> data;
final Animation<double> animation;
static const int gridLines = 10;
static const double gridStrokeWidth = 0.5;
static const double dataStrokeWidth = 2.0;
static const Color backgroundColor = Color(0xFFF9F9F9);
static const Color gridColor = Color(0xFFE0E0E0);
static const Color dataColor = Colors.blue;
@override
void paint(Canvas canvas, Size size) {
_drawBackground(canvas, size);
_drawGrid(canvas, size);
_drawData(canvas, size);
}
void _drawBackground(Canvas canvas, Size size) {
final paint = Paint()..color = backgroundColor;
canvas.drawRect(Offset.zero & size, paint);
}
void _drawGrid(Canvas canvas, Size size) {
final paint = Paint()
..color = gridColor
..strokeWidth = gridStrokeWidth;
for (int i = 0; i <= gridLines; i++) {
final y = size.height * i / gridLines;
canvas.drawLine(
Offset(0, y),
Offset(size.width, y),
paint,
);
}
}
void _drawData(Canvas canvas, Size size) {
if (data.isEmpty) return;
final paint = Paint()
..color = dataColor
..strokeWidth = dataStrokeWidth;
for (int i = 0; i < data.length - 1; i++) {
canvas.drawLine(
_dataToOffset(data[i], size),
_dataToOffset(data[i + 1], size),
paint,
);
}
}
Offset _dataToOffset(DataPoint point, Size size) {
return Offset(
point.x * size.width,
size.height - (point.y * size.height),
);
}
@override
bool shouldRepaint(covariant ChartPainter oldDelegate) {
return data != oldDelegate.data ||
animation.value != oldDelegate.animation.value;
}
}
Google 的原则:
- 将复杂的绘制器拆分为小巧、专注的方法。
- 避免不必要的计算。使用常量来消除"魔法数字"。
动画:组合优于复杂
社区典型方法:
dart
class ComplexAnimationWidget extends StatefulWidget {
@override
State<ComplexAnimationWidget> createState() => _ComplexAnimationWidgetState();
}
class _ComplexAnimationWidgetState extends State<ComplexAnimationWidget>
with TickerProviderStateMixin {
late final AnimationController _controller;
late final AnimationController _secondaryController;
late final Animation<double> _scaleAnimation;
late final Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_secondaryController = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
));
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _secondaryController,
curve: Curves.easeInOut,
));
}
}
Google的方式:
dart
class FadeScaleWidget extends StatefulWidget {
const FadeScaleWidget({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
});
final Widget child;
final Duration duration;
@override
State<FadeScaleWidget> createState() => _FadeScaleWidgetState();
}
class _FadeScaleWidgetState extends State<FadeScaleWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
)..forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _controller.value,
child: Opacity(
opacity: _controller.value,
child: widget.child,
),
);
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Google 的方法:
- 构建小巧、可重用的动画 Widget。
- 通过组合它们来构建复杂的动画------除非必要,否则绝不协调多个控制器。
性能:衡量,不要做假设
社区经常过度使用 const
,认为它是万灵药。 Google 团队关注的是实际的运行时性能------而不是代码的表面美观。
社区过度使用 const
的例子:
dart
const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Text('Product Name'), // 这不应该是 `const`,因为它的值是动态的。
],
),
),
);
Google 的实际做法:
dart
class ProductCard extends StatelessWidget {
const ProductCard({
super.key,
required this.product,
});
final Product product;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_ProductImage(product: product),
const SizedBox(height: 8.0),
Text(product.name),
Text('$${product.price.toStringAsFixed(2)}'),
],
),
),
);
}
}
class _ProductImage extends StatelessWidget {
const _ProductImage({required this.product});
final Product product;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(
product.imageUrl,
loadingBuilder: _buildLoading,
errorBuilder: _buildError,
),
);
}
Widget _buildLoading(
BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress,
) {
if (loadingProgress == null) return child;
return const Center(child: CircularProgressIndicator());
}
Widget _buildError(
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
return const Icon(Icons.error);
}
}
状态管理:按需选择
Google 不强制使用某一种模式。他们务实地结合使用:
StatefulWidget
用于本地 UI 状态Provider
(或InheritedWidget
)用于共享应用状态Services
用于业务逻辑
文件组织:按功能划分,而非按层划分
Google 的 Flutter 项目不严格遵循整洁架构(Clean Architecture)。他们按功能进行结构组织:
vbnet
lib/
├── home/
│ ├── home_page.dart
│ └── home_service.dart
├── cart/
│ ├── cart_page.dart
│ └── cart_provider.dart
├── shared/
│ ├── widgets/
│ └── services/
"如果你在处理购物车功能,就去 cart/.
文件夹里找。而不是在 3 个不同的文件夹里找。"
为什么?
因为这使得功能更容易维护和上手。
测试:集成优于隔离
Google 更喜欢集成测试,而不是过度模拟的单元测试:
dart
testWidgets('User updates profile', (tester) async {
await tester.pumpWidget(const TestApp());
await tester.tap(find.text('Profile'));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(const Key('name_field')), 'New Name');
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
expect(find.text('Profile updated successfully'), findsOneWidget);
});
性能监控:优先进行Instrument
Google 不会猜测瓶颈,而是进行衡量:
dart
class PerformanceWidget extends StatelessWidget {
const PerformanceWidget({
super.key,
required this.name,
required this.child,
});
final String name;
final Widget child;
@override
Widget build(BuildContext context) {
if (kDebugMode) {
return _DebugPerformanceWrapper(
name: name,
child: child,
);
}
return child;
}
}
最终思考:简洁致胜
Google 的 Flutter 团队不会为了炫技而编写花哨的代码。 他们编写的是简单、易于维护、能解决实际问题的代码。
这对你意味着什么:
- 从简单开始。
- 衡量性能。
- 测试实际用户流程。
- 按功能组织代码。
- 组合小型 Widget。
让我们一起编写更好的 Flutter 代码,也欢迎关注我的公众号:OpenFlutter。