Flutterで背景画像を回転させる

[前々回](https://qiita.com/noboru_i/items/404a77d7f5383df76b8e)と[前回](https://qiita.com/noboru_i/items/86b7a48dd880efd31803)に引き続き、今度は背景に関わる部分です。

## 実装したもの

背景全体を回転させます。
画面内に収まっている画像を回転させるのはかんたんでしたが、"全体"ということで画面の高さと同じ幅を持つ画像を描画する必要があり、少し面倒でした。

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8708/206b79ff-fac0-f628-efd0-f3811cf90bc0.gif)

回転させている画像は、 https://material.io/resources/icons/?style=baseline からダウンロードしたSVG画像です。

## コード

全文は[こちら](https://github.com/noboru-i/flutter_sample/blob/master/lib/screens/rotate_background/rotate_background_screen.dart)。

## コード説明

```dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

class RotateBackgroundScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Rotate background'),
      ),
      body: Stack(
        children: <Widget>[
          _RotateBackground(),
          _Screen(),
        ],
      ),
    );
  }
}
```

画面全体としては、このようになります。
`Scaffold`の子要素として、"背景"Widgetと"その他画面要素"Widgetを重ねるイメージです。

```dart
class _RotateBackground extends StatefulWidget {
  @override
  _RotateBackgroundState createState() => _RotateBackgroundState();
}

```

AnimationController無しにrotateさせる方法が見当たらなかったので、StatefulWidgetとしています。

```dart
class _RotateBackgroundState extends State<_RotateBackground>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _turns;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 5),
    );
    _turns = _animationController
        .drive(
          CurveTween(curve: Curves.linear),
        )
        .drive(
          Tween<double>(begin: 0, end: 1),
        );

    _animationController.repeat();
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
```

5秒間で1回転するアニメーションを、リピートさせています。

```dart
  @override
  Widget build(BuildContext context) {
    final screenHeight = MediaQuery.of(context).size.height;
    return ClipRect(
      child: OverflowBox(
        minWidth: 0.0,
        minHeight: 0.0,
        maxWidth: screenHeight,
        maxHeight: screenHeight,
        child: RotationTransition(
          turns: _turns,
          alignment: Alignment.center,
          child: SvgPicture.asset(
            'assets/bg.svg',
            width: screenHeight,
            height: screenHeight,
          ),
        ),
      ),
    );
  }
}
```

`RotationTransition` に、先に出ていた `_trans` を渡すことで、`SvgPicture`が回転します。
`SvgPicture`に、表示領域の高さで縦横の大きさを与えているのですが、外側の `OverflowBox` を指定しない場合は、以下のように画面幅に制限されてしまいます。

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8708/44408dd1-64f5-335d-6106-bfc17c311932.gif)

さらに、その外側を `ClipRect` で囲むことで、以下のように画面遷移のタイミングで変に見えてしまうことを防いでいます。
（画面の大きさを飛び越えて、SVG画像が表示されてしまっている。）

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8708/606babec-f6e5-4c76-5f88-9423059b8ff2.gif)

## 終わり

`OverflowBox` や `ClipRect` を利用することで、画面外に画像を描画しつつ、アニメーションで見せることができました。
これを転用して、キャラクターが横から出たり引っ込んだりするUIも作れそうですね。

「どれだけのWidgetを知っているか」で実装できる幅が変わってきそうなので、 https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG を流し見ておくのは大事かもしれません。