[Flutter] Getxで AnimationControllerを使う方法

![get.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/c62bd5fa-dd9c-30bc-b36d-fd5c99df37b3.png)
上の画像は使っていいんですかね。。すごく壁紙？としていいなと思ったので使わせていただきました。



こんにちわ。いせきです。

色々な状態管理があり、人によって何を使うのかがバラバラですね。

僕は、最近GetXを使用して開発をすることが多いです。


ゲームのアプリを作成している段階で、ボタンにアニメーションをつけたいと思っていましたが、なかなかうまくいきませんでした。調べて、試してを繰り返した結果、実装することができたので、共有しようと思います。


# GetXとは、

公式では以下のように説明されています。

> GetX is an extra-light and powerful solution for Flutter. It combines high-performance state management, intelligent dependency injection, and route management quickly and practically.

詳しくは以下のサイトを参考にしてください。

https://pub.dev/packages/get


# 開発の流れ


今回は、画面を描画する（Screen）と処理などを記載する(Controller)の２ファイルで作成しました。
基本的な書き方などは、他の方が数百倍わかりやすく記載しているので、省略します。

```dart
 - lib
   - component
     - button_item
   - home_screen
     - home_screen.dart
     - home_screen_controller.dart 
```




こんなアニメーションをしたいと思います。見えづらいかもしれませんが、大きくなったり小さくなったりしています。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/02f57a55-4817-9dfb-5142-e250126ede72.gif", width=40%>





### ① AnimationControllerを使う前に必要な処理

GetXControllerの後に`with GetSingleTickerProviderStateMixin`をつけるとgetx内でもAnimationControllerに必要な`TickerProvider`が使えるようになりました。

```dart
class HomeScreenController extends GetxController with GetSingleTickerProviderStateMixin {
}
```
### ②Controller内にAnimationControllerのインスタンスを作成

今回は、1秒ごとに処理を何度も行うようにしました。
`repeat`を使うことで何度もアニメーションを使えるようになりました。

あと、`with GetSingleTickerProviderStateMixin`を使わなければいけない理由としては、

```
vsync: this,  のthisを入れられるようにするためです。
```



controller内の記載内容です。

```dart
 late AnimationController animationController;

 @override
  void onInit() {
    super.onInit();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(
         seconds: 1,
      ),
    )..repeat(reverse: true);
  }
```


### ③Screen内に記載

今回は、大きさ１.0倍から1.17倍に大きくなったり、小さくなったりするアニメーションを実装しました。

```dart
ScaleTransition(
  scale: controller.animationController.drive(
    Tween<double>(
      begin: 1,
      end: 1.17,
    ),
  ),
  child: ButtonItem(
  onPressed: controller.onTapLevel4,
  text: 'レベル４',
  ),
),
```


`ButtonItem`でComponent化していますが、無理にComponent化しなくてもOKです。



```dart:button_item.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class ButtonItem extends StatelessWidget {
  const ButtonItem({
    Key? key,
    required this.onPressed,
    required this.text,
  }) : super(key: key);

  final Function() onPressed;
  final String text;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Container(
        width: 280,
        height: 60,
        decoration: const BoxDecoration(
          borderRadius: BorderRadius.all(Radius.circular(25.0)),
          boxShadow: [
            BoxShadow(
              color: Colors.white,
              spreadRadius: 4,
              blurRadius: 10,
              offset: Offset(0, 3),
            ),
          ],
        ),
        child: Center(
          child: Text(
            text,
            style: const TextStyle(
              fontSize: 25,
              color: CupertinoColors.black,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
      style: ElevatedButton.styleFrom(
        primary: Colors.grey.shade100,
        onPrimary: Colors.grey.shade100,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10.0),
        ),
      ),
    );
  }
}

```


# ソースコード


```dart:home_screen 

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'component/button_item.dart';
import 'home_screen_controller.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //ここで入れることでHomeScreenControllerが使えるようになる！
    final controller = Get.put(HomeScreenController(), tag: '');
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white,
        leading: IconButton(
          iconSize: 35,
          icon: const Icon(
            Icons.arrow_back_ios,
            color: Colors.blueAccent,
          ),
          onPressed: controller.onTapBack,
        ),
        actions: [
          IconButton(
            onPressed: controller.onTapTrophyScreen,
            iconSize: 40,
            icon: const Icon(
              Icons.flag,
              color: Colors.black,
            ),
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ScaleTransition(
              scale: controller.animationController.drive(
                Tween<double>(
                  begin: 1,
                  end: 1.1,
                ),
              ),
              child: ButtonItem(
                onPressed: controller.onTapLevel1,
                text: 'レベル１',
              ),
            ),
            ScaleTransition(
              scale: controller.animationController.drive(
                Tween<double>(
                  begin: 1,
                  end: 1.13,
                ),
              ),
              child: ButtonItem(
                onPressed: controller.onTapLevel2,
                text: 'レベル２',
              ),
            ),
            ScaleTransition(
              scale: controller.animationController.drive(
                Tween<double>(
                  begin: 1,
                  end: 1.15,
                ),
              ),
              child: ButtonItem(
                onPressed: controller.onTapLevel3,
                text: 'レベル３',
              ),
            ),
            ScaleTransition(
              scale: controller.animationController.drive(
                Tween<double>(
                  begin: 1,
                  end: 1.17,
                ),
              ),
              child: ButtonItem(
                onPressed: controller.onTapLevel4,
                text: 'レベル４',
              ),
            ),
            ScaleTransition(
              scale: controller.animationController.drive(
                Tween<double>(
                  begin: 1,
                  end: 1.2,
                ),
              ),
              child: ButtonItem(
                onPressed: controller.onTapLevel5,
                text: 'レベル５',
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```

Preferenceの処理などは気にしないでね。。。



```dart:Home_screen_controller
import 'package:flutter/animation.dart';
import 'package:get/get.dart';
import '../../preference/shared_preference.dart';
import '../challenge_screen/challenge_screen_1.dart';
import '../challenge_screen/challenge_screen_2.dart';
import '../challenge_screen/challenge_screen_5.dart';

class HomeScreenController extends GetxController
    with GetSingleTickerProviderStateMixin {
  var stage1 = false.obs;
  var stage2 = false.obs;
  var stage3 = false.obs;
  var stage4 = false.obs;
  var stage5 = false.obs;

  late AnimationController animationController;

  @override
  void onInit() {
    super.onInit();
    sharedPreference();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(
        seconds: 1,
      ),
    )..repeat(reverse: true);
  }

  void onTapBack() {
    Get.back();
  }

  Future<void> sharedPreference() async {
    stage1.value = await Preference().getBool(PreferenceKey.stage1);
    stage2.value = await Preference().getBool(PreferenceKey.stage2);
    stage3.value = await Preference().getBool(PreferenceKey.stage3);
    stage4.value = await Preference().getBool(PreferenceKey.stage4);
    stage5.value = await Preference().getBool(PreferenceKey.stage5);
  }

  void onTapLevel1() {
    Get.to(() => const ChallengeScreen1());
  }

  void onTapLevel2() {
    Get.to(() => const ChallengeScreen2());
  }

  void onTapLevel3() {
    Get.to(() => const ChallengeScreen3());
  }

  void onTapLevel4() {
    Get.to(() => const ChallengeScreen5());
  }

  void onTapLevel5() {
    Get.to(() => const ChallengeScreen5());
  }

  void onTapTrophyScreen() {
    Get.to(
      () => TrophyScreen(
        isClear1: stage1.value,
        isClear2: stage2.value,
        isClear3: stage3.value,
        isClear4: stage4.value,
        isClear5: stage5.value,
      ),
    );
  }
}

```

# 参考文献

https://qiita.com/youmeee/items/bc2da00af12cce38a54f

https://stackoverflow.com/questions/67628795/how-to-use-animationcontroller-in-getx-instead-of-using-statefulwidget-and-singl

https://qiita.com/rei_012/items/aa5385ac9254d01dc54a　

https://pub.dev/packages/get

https://qiita.com/Qiita/items/c686397e4a0f4f11683d