ドキュメント
前提
- Flutterの公式のドキュメントのパフォーマンスの章の内容を復習する
- よく遭遇するパターンを掻い摘んでまとめます
- 説明をスムーズに行うために別のドキュメントの内容も一部記載する
環境
[✓] Flutter (Channel stable, 3.3.9, on macOS 12.6 21G115 darwin-arm
(Rosetta), locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK
version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.3)
[✓] Connected device (3 available)
[✓] HTTP Host Availabilit
setStateが呼ばれると小孫のWidgetがrebuildされる
Flutterのサンプルプロジェクトでボタンとテキストを別のWidgetに切り出した場合、rebuildの範囲は切り出した子のWidget内に止まります。
ボタンを押した後の Widget rebuild stats
を見るとAppBarなどはrebuildされていないことがわかります。
Sample | 切り出した場合 |
---|---|
同じインスタンスを使うことで無駄なrebuildを抑える
AnimationBuilderの実装パターンのように、小孫のWidgetで同じのインスタンスを使うことで子のWidgetのrebuildが発生しない。FlutterのアニメーションWidgetでは、アニメーションの影響を子のWidgetに影響を与えないようによく使われる実装パターンです。
AnimationBuilder ありコード
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
child: const Center(
child: Text('Whee!'),
),
),
builder: (BuildContext context, Widget? child) {
return Transform.rotate(
angle: _controller.value * 2.0 * math.pi,
child: child,
);
},
);
}
}
AnimationBuilder なしコード
class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
);
animation = controller
..addListener(() {
setState(() {});
// #docregion addListener
});
// #enddocregion addListener
controller.repeat();
}
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: animation.value * 2.0 * math.pi,
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
child: const Center(
child: Text('Whee!'),
),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
AnimationBuilder あり | AnimationBuilder なし |
---|---|
AnimationtBuilderを使ったコードでは、_MyStatefulWidgetStateがrebuildされていないことがわかります。
Constを使う
言わずもがなだと思うので省略
ほとんどのプロジェクトでflutter_lints が入っており prefer_const_constructorsが有効化されていると思うので、警告が出たらconstをつける
重たい処理を理解する
saveLayer()
panit処理で複数のレイヤーを合成し複合効果を持たせるときに使うsaveLayer() を過度に呼び出すと、ジャンクが発生する原因となります。saveLayer()はFlutterフレームワークで最も高価なメソッドの1つです。
saveLayer()で2つの長方形を合成するコード
void paint(Canvas canvas, Size size) {
Rect rect = Offset.zero & const Size(100, 100);
canvas.drawRect(
rect,
Paint()..color = Colors.red,
);
canvas.saveLayer(
null,
Paint()..blendMode = BlendMode.multiply,
);
canvas.drawRect(
rect.shift(const Offset(20, 20)),
Paint()..color = Colors.blue,
);
canvas.restore();
}
saveLayer()を使用するとコンテンツをオフスクリーンバッファーに描画する必要があり、レンダーターゲットの切り替えが発生する場合があります。レンダーターゲットの切り替えにより、大量のメモリチャーンが発生し、アプリが遅くなる可能性があります。
下記のメソッドの内部でsaveLayerが使用されているので、使う時には注意が必要です。
- ShaderMask
- ColorFilter
- Chip (disabledColorAlpha != 0xffのとき)
- Text (TextOverflow.fadeが使われているとき)
checkerboardOffscreenLayersをtrueにすることで、offscreen layersが使われいてる箇所をアプリで確認することができます。
MaterialApp(
checkerboardOffscreenLayers: true,
...
)
Opacity
Opacityはコストのかかる操作になります。
Opacity Widgetを使用せずに直接Opacityをかける方が高速です。
- Colorの例
- GOOD:
Container(color: Color.fromRGBO(255, 0, 0, 0.5))
- BAD:
Opacity(opacity: 0.5, child: Container(color: Colors.red))
- GOOD:
- Imageの例
- GOOD:
Image.network('https://hoge.jp/icon.jpeg', color: const Color.fromRGBO(255, 0, 0, 0.5))
- BAD:
Opacity(opacity: 0.5m child: Image.network('https://hoge.jp/icon.jpeg', color: const Color.fromRGBO(255, 255, 255, 0.5)))
- GOOD:
Opacityをアニメーションさせるときは、AnimatedOpacityやFadeTransitionを使用することを検討する。
Clip.antiAliasWithSaveLayer
Opacityほどコストのかかるではありませんが、コストのかかる操作になります。
角丸の実装などではClipを使わずに、borderRadiusを使うようにする
ListViewを使うときは画面内の要素のみを描画する
2021年のGoogle I/Oの 「Flutter で実現する遅延読み込みのパフォーマンス」のセッションがわかりやすいので貼っておきます。