[Flutter]画面全体のどこをタップしてもイベント(主にアニメーション)を実行させる方法

この記事は[全部俺 Advent Calendar 2018](https://adventar.org/calendars/3702)の9日目の記事です。
毎回毎回調べ直している気がするので書きました。

# やりたいこと
![animation_button.gif](https://qiita-image-store.s3.amazonaws.com/0/190807/8197b48f-8604-92f1-c4a9-4cd51aad2912.gif) ![animation_screen.gif](https://qiita-image-store.s3.amazonaws.com/0/190807/056b073c-c84b-1424-b770-94e7884948c4.gif)

左のようなボタンを押して起きるイベントを、右のように画面のどこをタップしてもできるようにします。

## ソースコード
### ボタンでイベント発火するソースコード
```fade_in_with_button.dart
import 'package:flutter/material.dart';

class FadeInPage extends StatefulWidget {
  @override
  _FadeInPageState createState() => _FadeInPageState();
}

// The State class is responsible for two things: holding some data we can
// update and building the UI using that data.
class _FadeInPageState extends State<FadeInPage> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:Text("Fade in / Fade out by toggle button"),
      ),
      body: Center(
        child: AnimatedOpacity(
          // If the Widget should be visible, animate to 1.0 (fully visible). If
          // the Widget should be hidden, animate to 0.0 (invisible).
          opacity: _visible ? 1.0 : 0.0,
          duration: Duration(milliseconds: 500),
          // The green box needs to be the child of the AnimatedOpacity
          child: Container(
            width: 200.0,
            height: 200.0,
            color: Colors.green,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Make sure we call setState! This will tell Flutter to rebuild the
          // UI with our changes!
          setState(() {
            _visible = !_visible;
          });
        },
        tooltip: 'Toggle Opacity',
        child: Icon(Icons.flip),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```
`floatingActionButton`内に`onPressed`イベントを定義することでイベントを実行することを可能にしています。
ここでは`_visible`のトグルを行っています。

### 画面全体のどこを押してもイベント発火するサンプルコード
```fade_in_whole_screen.dart
import 'package:flutter/material.dart';

class FadeInPage extends StatefulWidget {
  @override
  _FadeInPageState createState() => _FadeInPageState();
}

// The State class is responsible for two things: holding some data we can
// update and building the UI using that data.
class _FadeInPageState extends State<FadeInPage> {
  // Whether the green box should be visible or invisible
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Fade in / Fade out by whole screen"),
        ),
        body: GestureDetector(
            behavior: HitTestBehavior.opaque,
            child: Center(
              child: AnimatedOpacity(
                // If the Widget should be visible, animate to 1.0 (fully visible). If
                // the Widget should be hidden, animate to 0.0 (invisible).
                opacity: _visible ? 1.0 : 0.0,
                duration: Duration(milliseconds: 500),
                // The green box needs to be the child of the AnimatedOpacity
                child: Container(
                  width: 200.0,
                  height: 200.0,
                  color: Colors.green,
                ),
              ),
            ),
            onTap: () {
              setState(() {
                _visible = !_visible;
              });
            }));
  }
}
```
`body`部分全体を`GestureDetector`で囲い、先程のソースコード内の`onPressed`に定義されていた内容を`onTap`に記載します。
肝になるのが、`behavior: HitTestBehavior.opaque`という部分です。
ここを記載しない場合、`GestureDetector`のデフォルトの`behavior`が使用されますが、デフォルトでは`behavior: HitTestBehavior.deferToChild`という挙動になります。
このデフォルト挙動は、子要素がタップされたときにイベントを発火させるというものなので、デフォルトでは画面全体をタップしてもイベント発火はしません。（画面上の緑色四角部分をタップするとイベント発火します。）

ソースコード全体は[ここ](https://github.com/taishi-h/flutter-animation-sample)においてあります。

## 参考文献
- [Google公式クックブックのアニメーションサンプル](https://flutter.io/docs/cookbook/animation/opacity-animation)