👋

FlutterのRepaintBoundaryを使って再描画範囲を分離するのを試してみた

2024/05/03に公開

こんにちは、今回はFlutterのWidget、RepaintBoundaryを試してみたので、コードとともに紹介します。

https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html

RepaintBoundaryを使うと、子Widgetの描画を分離して、必要のない親Widgetの再描画を防ぐことができます。
ネットで検索しても、意外にシンプルな日本語の比較記事がなかったので、残しておきます。

実験内容

GridViewを使って、3つのケースを試して、再描画範囲がどうなるかを検証

  • ケース1: RepaintBoundary使わない
  • ケース2: RepaintBoundary使う
  • ケース3: RepaintBoundary使う(SizedBox使わない)

再描画は、1秒ごとにTextの文字列をランダムに変えることで起きるようにする。

    final cards = [
      // ケース1: RepaintBoundary使わない
      Container(
        color: Colors.grey,
        child: Center(
          child: SizedBox(
            width: 100,
            height: 50,
            child: Text(displayText.value),
          ),
        ),
      ),
      // ケース2: RepaintBoundary使う
      Container(
        color: Colors.grey,
        child: Center(
          child: RepaintBoundary(
            child: SizedBox(
              width: 100,
              height: 50,
              child: Text(displayText.value),
            ),
          ),
        ),
      ),
      // ケース3: RepaintBoundary使う(SizedBox使わない)
      Container(
        color: Colors.grey,
        child: Center(
          child: RepaintBoundary(
            child: Text(displayText.value),
          ),
        ),
      ),
    ];

コード全体例

import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

import 'package:hooks_riverpod/hooks_riverpod.dart';

class RepaintBoundaryPage extends HookConsumerWidget {
  const RepaintBoundaryPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final displayText = useState<String>('text0');

    useEffect(() {
      // 1秒ごとに表示するTextの内容を変えて再描画を起こす
      final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
        final random = math.Random();
        final randomNumber = random.nextInt(10);
        displayText.value = 'text$randomNumber';
      });

      return timer.cancel;
    }, []);

    final cards = [
      // ケース1: RepaintBoundary使わない
      Container(
        color: Colors.grey,
        child: Center(
          child: SizedBox(
            width: 100,
            height: 50,
            child: Text(displayText.value),
          ),
        ),
      ),
      // ケース2: RepaintBoundary使う
      Container(
        color: Colors.grey,
        child: Center(
          child: RepaintBoundary(
            child: SizedBox(
              width: 100,
              height: 50,
              child: Text(displayText.value),
            ),
          ),
        ),
      ),
      // ケース3: RepaintBoundary使う(SizedBox使わない)
      Container(
        color: Colors.grey,
        child: Center(
          child: RepaintBoundary(
            child: Text(displayText.value),
          ),
        ),
      ),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('RepaintBoundary Page'),
      ),
      body: GridView.builder(
        itemCount: cards.length,
        itemBuilder: (context, index) {
          return cards[index];
        },
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 3,
          crossAxisSpacing: 3,
        ),
      ),
    );
  }
}

結果

devtoolsのinscepctorのHighlight repaintsの結果が以下の画像です

https://docs.flutter.dev/tools/devtools/inspector#highlight-repaints

ケース 結果
ケース1: RepaintBoundary使わない 親WidgetのContainerが再描画されている
ケース2: RepaintBoundary使う RepaintBoundaryの子のSizedBoxが再描画されている
再描画の分離ができている
ケース3: RepaintBoundary使う
(SizedBox使わない)
TextContainerが再描画されている

よって、RepaintBoudaryを使うことにより、再描画範囲を分離する(ケース2)ことができました。
ただし、ケース3のように、RepaintBoundaryを使っても再描画範囲を分離できない場合もあることがわかりました。
SizedBoxなどを使って、明示的に大きさを分けることが重要なのかな?と思います。

Discussion