返回 Skill 列表
extension
分类: 开发与工程无需 API Key

flutter-performance-docs

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

person作者: jakexiaohubgithub

Flutter Performance Best Practices

The 16ms Rule

| Display | Frame Budget | Build | Render | |---------|--------------|-------|--------| | 60Hz | 16ms | ~8ms | ~8ms | | 120Hz | 8ms | ~4ms | ~4ms |

Always profile in profile mode, not debug mode.


1. Control build() Cost

Split Large Widgets

// 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()

// 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

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

Prefer StatelessWidget Over Functions

// 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

// 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.

// 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

// 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

// 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

// 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

// 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

// 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-Pattern | Solution | |--------------|----------| | Opacity widget in animations | AnimatedOpacity, FadeInImage | | ListView(children: [...]) | ListView.builder() | | ClipRRect in animations | Pre-clip before animating | | Override operator == on Widget | Only for leaf widgets with efficient comparison | | Large monolithic widgets | Split into smaller widgets | | setState() high in tree | Localize to affected subtree | | IntrinsicHeight/Width | Fixed sizes or custom RenderObject |


Debugging Tools

| Tool | Purpose | |------|---------| | DevTools Performance View | Timeline, frame analysis | | DevTools Inspector | Track widget rebuilds | | "Track layouts" option | Find intrinsic passes | | checkerboardOffscreenLayers | Find saveLayer calls | | Profile mode build | Accurate performance measurement |


Mobile: Use Impeller

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

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

Official Docs