Flutter InteractiveViewerの使い方

## はじめに

Flutter 1.20で新規追加された、`InteractiveViewer`の使い方についてまとめています。

これまではインタラクティブにWidgetの拡大縮小や移動が面倒でしたが、今後は`InteractiveViewer`がとても便利です。また、これを使うと楽しそうな事が出来ると思いますので、ぜひ使ってみて下さい。

## デモ
![qiita_0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/e7437801-6242-6f20-3772-e938bb4a7a7b.gif)

## 使い方

### サンプルソースコード

```Dart
class _DemoPageState extends State<DemoPage> {
  final _transformationController = TransformationController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: InteractiveViewer(
          alignPanAxis: false,
          constrained: true,
          panEnabled: true,
          scaleEnabled: true,
          boundaryMargin: EdgeInsets.all(256.0),
          minScale: 0.1,
          maxScale: 10.0,
          onInteractionStart: (details) =>
              print('onInteractionStart: ' + details.toString()),
          onInteractionEnd: (details) {
              print('onInteractionEnd: ' + details.toString());
              setState(() {
                // コメントを外すと、操作後に初期位置に戻る
                //_transformationController.value = Matrix4.identity();
              });
          },
          onInteractionUpdate: (details) =>
              print('onInteractionUpdate: ' + details.toString()),
          transformationController: _transformationController,
          child: Center(
            child: Ink(
              decoration: const ShapeDecoration(
                color: Colors.lightBlue,
                shape: CircleBorder(),
              ),
              child: IconButton(
                icon: Icon(Icons.android),
                color: Colors.white,
                onPressed: () {},
              ),
            ),
          ),
        ));
  }
}
```

### プロパティ

|プロパティ名|説明|初期値|
|---------|---|-----|
|alignPanAxis|trueに設定すると、Panning/パニング (移動)が上下左右の4方向に固定される|false|
|constrained|子Widgetが親Widgetよりも大きい場合にfalseにします。この場合にはfalseにしないとPanningが機能しません。以下の具体例を確認して下さい。|true|
|panEnabled|Panningの有効無効設定|true|
|scaleEnabled|スケーリングの有効無効設定|true|
|boundaryMargin|Panning可能なマージンサイズを指定。0だとPanningは不可|EdgeInsets.zero|
|minScale|最小の縮小率 (例えば、0.1の場合は10%)。0.1未満は機能しない気がします。|0.8|
|maxScale|最大の拡大率|2.5|
|onInteractionStart|インタラクション開始時にコールバック|null|
|onInteractionEnd|インタラクション終了時にコールバック|null|
|onInteractionUpdate|インタラクション更新毎時にコールバック|null|
|transformationController|必要ならTransformationControllerを指定|null|
|child|説明は割愛|null|

### TransformationControllerの利用例

`TransformationController`を利用する事で、インタラクション後に初期状態に戻るなどのアクションを追加する事が可能です。
![qiita_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/ecf6849d-5b3e-a209-7127-e6b1958282f7.gif)

### constrainedプロパティの補足

以下のサンプルの様に、子Widgetが親Widgetよりも大きなViewを表示する場合に`constrained`プロパティをfalseにする必要があります。

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/46d0e350-c559-be9e-5da7-97afcf95819b.gif)


```Dart
  @override
  Widget build(BuildContext context) {
    const int _rowCount = 20;
    const int _columnCount = 3;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pannable Table'),
      ),
      body: InteractiveViewer(
        constrained: false,
        child: Table(
          columnWidths: <int, TableColumnWidth>{
            for (int column = 0; column < _columnCount; column += 1)
              column: const FixedColumnWidth(300.0),
          },
          children: <TableRow>[
            for (int row = 0; row < _rowCount; row += 1)
              TableRow(
                children: <Widget>[
                  for (int column = 0; column < _columnCount; column += 1)
                    Container(
                      height: 100,
                      color: row % 2 + column % 2 == 1 ? Colors.red : Colors.green,
                    ),
                ],
              ),
          ],
        ),
      ),
    );
  }
```

## 参考文献

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