
效能足以決定一個 Flutter 應用程式的成敗。用戶期望流暢的捲動、即時的回應和省電的表現。即使應用程式再精美,一旦感覺遲緩或耗電嚴重,也難逃失敗。
在優化過數十個 Flutter 應用程式之後,我們整理出一批持續見效的最有效技巧。這些不只是紙上談兵的理論——而是經過實戰考驗、確實改善過生產環境中真實應用程式的策略。
處處使用 const constructor
這是效能優化中最垂手可得的成果。const constructor 會建立編譯期常數,讓 Flutter 可以進行更積極的優化,減少 widget 重建和記憶體分配。
// ❌ Bad
Container(
padding: EdgeInsets.all(16),
child: Text('Hello'),
)
// ✅ Good
const Container(
padding: const EdgeInsets.all(16),
child: const Text('Hello'),
)實作高效的清單建構
處理長清單時,務必使用 ListView.builder() 而非 ListView()。它會按需建立 widget,而不是一次過全部建立,大幅減少記憶體用量和初始載入時間。
// ❌ Bad - Creates all widgets at once ListView( children: items.map((item) => ItemWidget(item)).toList(), ) // ✅ Good - Creates widgets on demand ListView.builder( itemCount: items.length, itemBuilder: (context, index) => ItemWidget(items[index]), )
優化圖片載入
圖片往往是最大的效能瓶頸。使用 cacheWidth 和 cacheHeight 在記憶體中調整圖片尺寸,避免 OOM 錯誤並提升捲動效能。
// ✅ Optimized image loading
Image.network(
imageUrl,
cacheWidth: 400,
cacheHeight: 400,
fit: BoxFit.cover,
)
// For hero images, use precacheImage
@override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(NetworkImage(heroImageUrl), context);
}策略性使用 RepaintBoundary
RepaintBoundary 會為 widget 建立獨立圖層,避免 UI 其他部分更新時,複雜 widget 被不必要地重繪。適用於靜態內容或繪製成本高的 widget。
// Wrap expensive widgets
RepaintBoundary(
child: ComplexCustomPaintWidget(),
)
// Check paint performance
void checkNeedsRepaint() {
final RenderRepaintBoundary boundary =
_repaintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
print('Needs repaint: ${boundary.debugNeedsPaint}');
}實作分頁與延遲載入
不要一次過載入所有資料。為清單實作分頁、為內容實作延遲載入,既能縮短初始載入時間、減少記憶體用量,也能改善用戶的體感效能。
class PaginatedList extends StatefulWidget {
@override
_PaginatedListState createState() => _PaginatedListState();
}
class _PaginatedListState extends State<PaginatedList> {
final _scrollController = ScrollController();
List<Item> _items = [];
bool _isLoading = false;
int _page = 1;
@override
void initState() {
super.initState();
_loadMore();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent * 0.8) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() => _isLoading = true);
final newItems = await fetchItems(page: _page);
setState(() {
_items.addAll(newItems);
_page++;
_isLoading = false;
});
}
}盡量減少 widget 重建
善用 const widget、妥善運用 Key,並拆分 widget 以減少不必要的重建。把靜態部分抽取成獨立 widget,並使用 ValueListenableBuilder 做針對性更新。
// ❌ Bad - Entire widget rebuilds
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
ExpensiveStaticWidget(), // Rebuilds unnecessarily
Text('Count: $_counter'),
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: Text('Increment'),
),
],
);
}
}
// ✅ Good - Only counter rebuilds
class OptimizedCounterWidget extends StatelessWidget {
final _counter = ValueNotifier<int>(0);
@override
Widget build(BuildContext context) {
return Column(
children: [
const ExpensiveStaticWidget(), // Never rebuilds
ValueListenableBuilder<int>(
valueListenable: _counter,
builder: (context, value, child) {
return Text('Count: $value');
},
),
ElevatedButton(
onPressed: () => _counter.value++,
child: const Text('Increment'),
),
],
);
}
}優化動畫
動畫請使用 AnimatedBuilder 而非 setState,確保只有動畫中的 widget 重建,而非整棵 widget 樹。同時要妥善 dispose 動畫,防止記憶體洩漏。
// ✅ Efficient animation
class FadeBox extends StatefulWidget {
@override
_FadeBoxState createState() => _FadeBoxState();
}
class _FadeBoxState extends State<FadeBox>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _animation.value,
child: child,
);
},
child: const ExpensiveWidget(), // Built only once
);
}
}以 isolate 處理繁重運算
把繁重的運算移到 isolate,避免阻塞 UI 執行緒,包括 JSON 解析、圖像處理或複雜計算。讓主執行緒保持空閒,UI 更新才會流暢。
import 'dart:isolate';
// Heavy computation in isolate
Future<List<ProcessedData>> processDataInIsolate(String rawData) async {
final p = ReceivePort();
await Isolate.spawn(_processData, p.sendPort);
final response = await p.first;
return response as List<ProcessedData>;
}
void _processData(SendPort p) {
// Heavy processing here
final processed = parseAndTransformData(largeDataSet);
Isolate.exit(p, processed);
}
// Using compute for simpler cases
import 'package:flutter/foundation.dart';
Future<String> parseJson(String json) async {
return await compute(_parseJsonInBackground, json);
}
String _parseJsonInBackground(String json) {
// Parse large JSON
final parsed = jsonDecode(json);
return processData(parsed);
}實作妥善的快取機制
為成本高的操作、API 回應和計算結果建立快取。圖片可使用 cached_network_image 之類的套件,並為經常存取的資料實作記憶體快取。
// Simple memory cache implementation
class CacheManager {
static final _cache = <String, CacheEntry>{};
static const _maxCacheSize = 100;
static const _defaultTTL = Duration(minutes: 5);
static T? get<T>(String key) {
final entry = _cache[key];
if (entry == null || entry.isExpired) {
_cache.remove(key);
return null;
}
return entry.value as T;
}
static void set<T>(String key, T value, {Duration? ttl}) {
if (_cache.length >= _maxCacheSize) {
// Remove oldest entries
_removeOldest();
}
_cache[key] = CacheEntry(
value: value,
expiry: DateTime.now().add(ttl ?? _defaultTTL),
);
}
static void _removeOldest() {
final sortedKeys = _cache.keys.toList()
..sort((a, b) => _cache[a]!.expiry.compareTo(_cache[b]!.expiry));
for (var i = 0; i < _maxCacheSize ~/ 4; i++) {
_cache.remove(sortedKeys[i]);
}
}
}
class CacheEntry {
final dynamic value;
final DateTime expiry;
CacheEntry({required this.value, required this.expiry});
bool get isExpired => DateTime.now().isAfter(expiry);
}剖析並量度一切
使用 Flutter DevTools 剖析你的應用程式。在開發階段啟用 performance overlay,追蹤幀渲染時間並找出卡頓。優化前後都要量度,以驗證改善成效。
// Enable performance overlay
MaterialApp(
showPerformanceOverlay: true, // Shows FPS and frame time
home: MyApp(),
)
// Track custom performance metrics
class PerformanceTracker {
static final _timers = <String, Stopwatch>{};
static void startTimer(String operation) {
_timers[operation] = Stopwatch()..start();
}
static Duration? endTimer(String operation) {
final timer = _timers[operation];
if (timer == null) return null;
timer.stop();
final duration = timer.elapsed;
_timers.remove(operation);
print('$operation took: ${duration.inMilliseconds}ms');
// Log slow operations
if (duration.inMilliseconds > 16) {
print('⚠️ SLOW OPERATION: $operation');
}
return duration;
}
}
// Usage
PerformanceTracker.startTimer('expensive_build');
// ... expensive operation ...
PerformanceTracker.endTimer('expensive_build');

