【Flutterレイアウト】Overlayの使用

Flutterレイアウトシリーズのその他の記事
- [Stackの使い方](https://qiita.com/ling350181/items/f5719272a516889bd3bb)
- [Flutterのレイアウトを理解する](https://qiita.com/ling350181/items/bfb4b715b56f17f75bb6)
- [FlutterレイアウトのFlex、Row、Column、Wrap、Stack](https://qiita.com/ling350181/items/47c8029e53af392f0e5f)
- [FlutterレイアウトのExpanded、Flexible、Spacer](https://qiita.com/ling350181/items/40d723da418ebe64d8d5)
- [FlutterのBoxConstraintsを理解する](https://qiita.com/ling350181/items/9e927826f1164c9205b1)


Overlayとは、言葉の通り、Widgetの上に被せるウェジェット部品とのことです。代表的なウェジェットはTooltipです。この記事はOverlayを使用、理解するため書いたものです。

※本記事は下記のZenn本にまとめました。

https://zenn.dev/cxj/books/07def04506182d


# レンダリング(rendering)
Overlayを使用するには、Widgetのサイズ、offsetの値が必要です。そこで、contextのfindRenderObject()メソッドからrenderingを取得することが可能です。
```dart
RenderObject? renderObject = context.findRenderObject();
if (renderObject != null && renderObject is RenderBox) {
  print(renderObject.size);
  print(renderObject.localToGlobal(Offset.zero));
}
```
# BuildContextの取得
UIツリーのScaffoldなど親ウェジェットを使うとき、contextは一番上層であるScaffoldのものが多いですが、ウェジェットを分離して、子ウェジェットのcontextを取得することも可能ですが、Builderウェジェットを使い、各自のContextを取得することも可能です。
例：
```dart
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('HomePage'),
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text("テキスト"),
            Builder(
              builder: (BuildContext ctx) => GestureDetector(
                onTap: () {
                  printInfo(ctx);
                },
                child: Container(
                  padding: const EdgeInsets.all(3),
                  child: const Text(
                    "context",
                    style: TextStyle(color: Colors.red),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void printInfo(BuildContext context) {
    RenderObject? renderObject = context.findRenderObject();
    if (renderObject != null && renderObject is RenderBox) {
      print(renderObject.size);
      print(renderObject.localToGlobal(Offset.zero));
    }
  }
```
# Overlayの使用
上記の方法でoffsetを取得し、OverlayStateにOverlayEntryを追加することができます。Overlayの作成は下記の流れです。
- OverlayStateの取得
```dart
final OverlayState? overlayState = Overlay.of(context);
```
- OverlayEntryの作成
```dart
// offset
Offset targetOffset = Offset.zero;
RenderObject? renderObject = context.findRenderObject();
if (renderObject != null && renderObject is RenderBox) {
  targetOffset = renderObject.localToGlobal(Offset.zero);
}
final OverlayState? overlayState = Overlay.of(context);
if (overlayState == null) return;
// OverlayEntryの作成
OverlayEntry entry = OverlayEntry(
  builder: (BuildContext context) => Positioned(
       width: 50,
       height: 50,
       left: targetOffset.dx,
       top: targetOffset.dy,
       child: GestureDetector(
          onTap: removeEntry,
          child: const ColoredBox(
          color: Colors.red,
        ),
     ),
  ),
);
```
- OverlayEntryのinsert
```dart
overlayState.insert(entry);
```
- Overlayの取り消し
```dart
  void removeEntry() {
    if (_entry != null) {
      _entry!.remove();
      _entry = null;
    }
  }
```