スクロールが不自然だからといって、全 Widget に const を付ければ解決するわけではない。Dart 側の構築、画像のデコード、ラスタライズでは対策が異なる。
再現できる操作を測る
実機の profile モードで画面、データ、ジェスチャーを固定する。遅いフレームの時刻を記録し、時間軸上の UI と raster の負荷を操作と対応付ける。ホットリロード、ブレークポイント、初回描画の影響を除き、一度のピークだけで判断しない。
UI 側が重ければ build 内の JSON 解析や並べ替え、上位状態変更による一覧全体の再構築を調べる。計算をデータ層に移し、監視範囲を狭め、長い一覧は ListView.builder で必要な行だけ作る。raster 側なら大きな画像、ぼかし、クリップ、画面外への描画を確認する。
修正前後を比較する
一度に一要因だけ変え、同じ端末で遅いフレーム数、最大時間、メモリを記録する。低性能端末と大きな文字でも再測定する。参考:Flutter UI パフォーマンス。
遅いフレームと処理側を特定する
商品詳細の画像カルーセルを開くとフレームが落ちる場合、勘で Widget を書き直さない。対象端末の profile モードで同じ操作を録画し、DevTools の frame chart と timeline を見る。1 フレームの予算は更新周波数で変わり、60 Hz では約 16.7 ms、120 Hz では約 8.3 ms だ。UI 側の超過は build/layout や Dart の計算、raster 側の超過は複雑なレイヤー、クリップ、ぼかし、画像処理を疑う。平均 FPS だけでなく遅い側のフレーム時間を記録する。
UI が重ければ、どの状態更新でページ全体が再構築されたかを追い、頻繁な価格更新を局所的な購読にする。raster が重ければ、画像のデコード寸法や透明レイヤー・影の重なりを確認する。RepaintBoundary は繰り返す描画を隔離して効果がある場合に使い、メモリ増加も見る。重い画像処理や JSON 解析は isolate を検討できるが、データ転送の費用を含めて判断し、小さな仕事を何でも移さない。
再現 → profile 記録 → 予算超過フレーム → UI / raster を分類
→ 原因を一つ仮定 → 一箇所変更 → 同端末で再測定
→ メモリと表示結果を確認
回帰条件を決める
初回表示、スクロール中、アニメーション終了後を分けて測る。修正後は遅いフレームの時間に加え、操作可能までの時間、画像の鮮明さ、最大メモリ、アクセシビリティのフォーカスも確認する。低性能 Android と高更新周波数の iPhone で原因が異なる場合がある。少なくとも再現した端末では前後を比較する。測定が揺れるなら通信結果とデータを固定し、複数回試してから改善を判断する。
