AgentSkillsCN

flutter-performance-docs

【Flutter】Flutter性能最佳实践和优化指南。构建成本、渲染、列表、动画和反模式。(项目)

SKILL.md
--- frontmatter
name: flutter-performance-docs
description: "[Flutter] Flutter performance best practices and optimization guide. Build cost, rendering, lists, animations, and anti-patterns. (project)"

Flutter Performance Best Practices

The 16ms Rule

DisplayFrame BudgetBuildRender
60Hz16ms~8ms~8ms
120Hz8ms~4ms~4ms

Always profile in profile mode, not debug mode.


1. Control build() Cost

Split Large Widgets

dart
// BAD: Monolithic widget
class MyPage extends StatelessWidget {
  Widget build(context) => Column(children: [header, content, footer]);
}

// GOOD: Split into smaller widgets
class MyPage extends StatelessWidget {
  Widget build(context) => Column(children: [
    HeaderWidget(),
    ContentWidget(),
    FooterWidget(),
  ]);
}

Localize setState()

dart
// BAD: setState high in tree
class Parent extends StatefulWidget {
  void update() => setState(() {}); // Rebuilds entire subtree
}

// GOOD: setState only where needed
class Child extends StatefulWidget {
  void update() => setState(() {}); // Only rebuilds this widget
}

Use const Constructors

dart
// GOOD: Flutter skips rebuild for const widgets
const Text('Hello');
const SizedBox(height: 16);
const MyCustomWidget();

Prefer StatelessWidget Over Functions

dart
// BAD: Function returns widget
Widget buildHeader() => Container(...);

// GOOD: StatelessWidget (enables const, better rebuild tracking)
class Header extends StatelessWidget {
  const Header();
  Widget build(context) => Container(...);
}

2. Lists & Grids

Use Lazy Builders

dart
// BAD: Builds all items at once
ListView(children: items.map((i) => ItemWidget(i)).toList())

// GOOD: Only builds visible items
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ItemWidget(items[index]),
)

Avoid Intrinsic Passes

Intrinsic passes poll all cells for sizing - expensive for large grids.

dart
// BAD: Causes intrinsic pass
IntrinsicHeight(child: Row(children: [...]))

// GOOD: Fixed sizes
SizedBox(height: 100, child: Row(children: [...]))

Debug: Enable "Track layouts" in DevTools to see intrinsic timeline events.


3. Minimize saveLayer()

saveLayer() allocates offscreen buffer - expensive!

Widgets That Trigger saveLayer()

  • ShaderMask
  • ColorFilter
  • Chip (if disabledColorAlpha != 0xff)
  • Text (with overflowShader)

Debug

Enable PerformanceOverlayLayer.checkerboardOffscreenLayers in DevTools.


4. Opacity & Clipping

Opacity

dart
// BAD: Wraps widget in Opacity
Opacity(opacity: 0.5, child: Image(...))

// GOOD: Apply directly to image
Image(..., color: Colors.white.withOpacity(0.5), colorBlendMode: BlendMode.modulate)

// GOOD: For text, use semitransparent color
Text('Hello', style: TextStyle(color: Colors.black54))

// GOOD: For animations
AnimatedOpacity(opacity: _visible ? 1.0 : 0.0, child: ...)
FadeInImage(placeholder: ..., image: ...)

Clipping

dart
// BAD: Explicit clipping
ClipRRect(borderRadius: BorderRadius.circular(8), child: Container(...))

// GOOD: Use decoration borderRadius
Container(
  decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
  child: ...
)

Default is Clip.none - enable clipping only when needed.


5. Animations

TransitionBuilder Pattern

dart
// BAD: Rebuilds everything
AnimatedBuilder(
  animation: _controller,
  builder: (context, child) => Transform.rotate(
    angle: _controller.value,
    child: ExpensiveWidget(), // Rebuilt every frame!
  ),
)

// GOOD: Child is not rebuilt
AnimatedBuilder(
  animation: _controller,
  child: ExpensiveWidget(), // Built once
  builder: (context, child) => Transform.rotate(
    angle: _controller.value,
    child: child, // Reused
  ),
)

Pre-clip Images for Animation

dart
// BAD: Clips during animation
AnimatedBuilder(
  builder: (_, __) => ClipRRect(
    borderRadius: BorderRadius.circular(8),
    child: Image(...),
  ),
)

// GOOD: Pre-clipped image
final clippedImage = ClipRRect(
  borderRadius: BorderRadius.circular(8),
  child: Image(...),
);
AnimatedBuilder(
  child: clippedImage,
  builder: (_, child) => Transform.scale(scale: _scale, child: child),
)

6. String Concatenation

dart
// BAD: Creates intermediate strings
String result = '';
for (var item in items) {
  result += item.toString(); // O(n²)
}

// GOOD: Single concatenation
final buffer = StringBuffer();
for (var item in items) {
  buffer.write(item.toString());
}
final result = buffer.toString(); // O(n)

Anti-Patterns Summary

Anti-PatternSolution
Opacity widget in animationsAnimatedOpacity, FadeInImage
ListView(children: [...])ListView.builder()
ClipRRect in animationsPre-clip before animating
Override operator == on WidgetOnly for leaf widgets with efficient comparison
Large monolithic widgetsSplit into smaller widgets
setState() high in treeLocalize to affected subtree
IntrinsicHeight/WidthFixed sizes or custom RenderObject

Debugging Tools

ToolPurpose
DevTools Performance ViewTimeline, frame analysis
DevTools InspectorTrack widget rebuilds
"Track layouts" optionFind intrinsic passes
checkerboardOffscreenLayersFind saveLayer calls
Profile mode buildAccurate performance measurement

Mobile: Use Impeller

Impeller is Flutter's default graphics renderer. Eliminates shader compilation jank.

bash
# Verify Impeller is enabled (default on iOS/Android)
flutter run --enable-impeller

Official Docs