Dart
Flutter

Flutterレイアウトのビジュアルデバッグ機能

Flutterにはレイアウトを視覚的にデバッグするために、topレベルのプロパティとして、いくつかbooleanフラグが用意されています。
https://flutter.io/debugging/#visual-debugging

全てではありませんが、Flutter Widget Inspectorから設定できるフラグもあります。


debugPaintSizeEnabled

Widgetの境界線は明るい緑掛かった水色(teal)、パッドやマージンは薄いブルー(faded blue)で塗りつぶすとともに、パディングしたchildのWidgetの境界線はダークブルーでふち取っています。ドキュメントに記述はありませんが、ListViewの下に伸びた緑の矢印は表示する軸の方向を表しているようです。

mainAxisAlignmentなどのアラインメントは黄色い矢印で表しています。スクリーンショットにはありませんが、childのないContainerなどをスペーサーとして配置しているとグレーで表示されるようです。


debugPaintBaselinesEnabled

debugPaintSizeEnabledと似ていますが、ベースラインが表示されます。

Flutter Widget InspectorのAdditional Actions>Show Paint Baselinesでも表示できます。


debugPaintPointersEnabled

タップした箇所が明るい緑掛かった水色(teal)で表示されます。ヒットテストに使用できます。

スクリーンショットはタップできないので割愛!


debugPaintLayerBordersEnabled

レイヤの境界線をオレンジ色で表示します。




compositor(composited?) layersのデバッグに使用する、といわれても何のことだかさっぱりですが、つらつら読んだ限りではcomposited layer単位に再描画を行っているようで、出来合いのWidgetを使用している限りlayerは所与ですが、再描画を行う範囲を明示的にRepaintBoundaryでラップすることにより、再描画の伝播を必要最小限に留めることができるので、このデバッグフラグはその範囲を見極めるために使用する、ということのようです。

以下はDebugging application layersにあるdebugDumpLayerTree()でlogcat出力したレイヤーツリーですが、先ほどのスクリーンショットで境界分けされたレイヤごとにcreatorとしてRepaintBoundaryが載っており、確かにcomposited layerとRepaintBoundaryは一対一の関係にありそうです。

logcat
...snip...
I/flutter:  │     │ │ │   ├─child 1: OffsetLayer#99b87
I/flutter:  │     │ │ │   │ │ creator: RepaintBoundary-[<0>] ←
I/flutter:  │     │ │ │   │ │   NotificationListener<KeepAliveNotification> ← KeepAlive ←
I/flutter:  │     │ │ │   │ │   AutomaticKeepAlive ← SliverList ← MediaQuery ← SliverPadding ←
I/flutter:  │     │ │ │   │ │   Viewport ← _ScrollableScope ← IgnorePointer-[GlobalKey#109c4] ←
I/flutter:  │     │ │ │   │ │   Semantics ← Listener ← ⋯
I/flutter:  │     │ │ │   │ │ offset: Offset(0.0, 0.0)
I/flutter:  │     │ │ │   │ │
I/flutter:  │     │ │ │   │ └─child 1: PictureLayer#555dd
I/flutter:  │     │ │ │   │     paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 240.0)
I/flutter:  │     │ │ │   │
I/flutter:  │     │ │ │   ├─child 2: OffsetLayer#6317d
I/flutter:  │     │ │ │   │ │ creator: RepaintBoundary-[<1>] ←
I/flutter:  │     │ │ │   │ │   NotificationListener<KeepAliveNotification> ← KeepAlive ←
I/flutter:  │     │ │ │   │ │   AutomaticKeepAlive ← SliverList ← MediaQuery ← SliverPadding ←
I/flutter:  │     │ │ │   │ │   Viewport ← _ScrollableScope ← IgnorePointer-[GlobalKey#109c4] ←
I/flutter:  │     │ │ │   │ │   Semantics ← Listener ← ⋯
I/flutter:  │     │ │ │   │ │ offset: Offset(0.0, 240.0)
I/flutter:  │     │ │ │   │ │
I/flutter:  │     │ │ │   │ └─child 1: PictureLayer#115df
I/flutter:  │     │ │ │   │     paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 104.0)
I/flutter:  │     │ │ │   │
I/flutter:  │     │ │ │   ├─child 3: OffsetLayer#35a4c
I/flutter:  │     │ │ │   │ │ creator: RepaintBoundary-[<2>] ←
I/flutter:  │     │ │ │   │ │   NotificationListener<KeepAliveNotification> ← KeepAlive ←
I/flutter:  │     │ │ │   │ │   AutomaticKeepAlive ← SliverList ← MediaQuery ← SliverPadding ←
I/flutter:  │     │ │ │   │ │   Viewport ← _ScrollableScope ← IgnorePointer-[GlobalKey#109c4] ←
I/flutter:  │     │ │ │   │ │   Semantics ← Listener ← ⋯
I/flutter:  │     │ │ │   │ │ offset: Offset(0.0, 344.0)
I/flutter:  │     │ │ │   │ │
I/flutter:  │     │ │ │   │ └─child 1: PictureLayer#8e164
I/flutter:  │     │ │ │   │     paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 70.0)
I/flutter:  │     │ │ │   │
I/flutter:  │     │ │ │   └─child 4: OffsetLayer#88221
I/flutter:  │     │ │ │     │ creator: RepaintBoundary-[<3>] ←
I/flutter:  │     │ │ │     │   NotificationListener<KeepAliveNotification> ← KeepAlive ←
I/flutter:  │     │ │ │     │   AutomaticKeepAlive ← SliverList ← MediaQuery ← SliverPadding ←
I/flutter:  │     │ │ │     │   Viewport ← _ScrollableScope ← IgnorePointer-[GlobalKey#109c4] ←
I/flutter:  │     │ │ │     │   Semantics ← Listener ← ⋯
I/flutter:  │     │ │ │     │ offset: Offset(0.0, 414.0)
I/flutter:  │     │ │ │     │
I/flutter:  │     │ │ │     └─child 1: PictureLayer#ac76c
I/flutter:  │     │ │ │         paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 208.0)
...snip...

試しに、アイコンごとにRepaintBoundaryでラップしたところ、レイヤの境界線は以下のようになりました。


debugRepaintRainbowEnabled

こちらもdebugPaintLayerBordersEnabled同様にレイヤの境界線を太枠で表していますが、再描画のたびに色が変わっていきます。本来は再描画された境界線のみが変色していることを確認する機能だとは思いますが、Hot Reloadボタンを何回か押せば色が変わっていくのを手っ取り早く確認できます。

Flutter Widget InspectorのAdditional Actions>Enable Repaint Rainbowでも表示できます。


Flutter Widget Inspectorから変更できるフラグもありますが、プログラム的にはmain()の先頭で設定します。

main.dart
import 'package:flutter/rendering.dart';

void main() {
  debugPaintSizeEnabled = true;
//  debugPaintBaselinesEnabled = true;
//  debugPaintPointersEnabled = true;
//  debugPaintLayerBordersEnabled = true;
//  debugRepaintRainbowEnabled = true;

  runApp(MyApp());
}

もちろんお望みなら、全部有効にすることも可能です。


debugShowMaterialGrid(MaterialApp)

MaterialAppには、Material Design baseline gridを表示するフラグが用意されています。
https://flutter.io/debugging/#material-grid

こちらはMaterialAppのコンストラクタで指定します。

    return MaterialApp(
      debugShowMaterialGrid: true,
      ...snip...
    );


ビジュアルデバッグ機能はうざいのでいずれオフると思いますが、Flutter全般で、debugほにゃららといったプロパティやメソッドはデバッグモードでしか効かないので、リリース時に残っていても無害です。


他にも、便利なデバッグ機能があります。読んどいたらよろし。
https://flutter.io/debugging/