Flutter로 일정 시간 후에 애니메이션 시작

만든 애니메이션



Flutter Web에서 스플래툰 2 플레이어용 동영상 플레이어 앱을 만들었습니다. 이 응용 프로그램은 커서 키의 좌우 또는 이미지 인식으로 추출된 장면의 시간 클릭으로 재생 위치를 점프 할 수 있습니다. 그 때 동영상 위로 점프처 시각을 컷 인 표시한 후, 0.7초 후에 0.3초에 걸쳐 페이드 아웃합니다.



※ 동영상 플레이어내에 표시되고 있는 동영상은 닌텐도 주식회사의 스플라툰 2로부터의 인용입니다.

만드는 법



Flutter에서 페이드 아웃과 같은 애니메이션을 수행하는 방법에는 Animated와 함께 Widget을 사용하는 방법이 있습니다. 페이드 아웃의 경우 AnimatedOpacity 클래스를 사용합니다. 그러나 그것만으로는 「0.7초 후에 애니메이션을 시작한다」라고 하는 설정을 할 수 없고, 타이머 클래스와 조합해 사용했습니다. 순서를 하나씩 해설합니다.

전제



앱은 다음 패키지를 사용하여 구축하고 있습니다.
  • hooks_riverpod
  • freezed

  • 시간 표시 상태를 나타내는 클래스 만들기



    video_time.dart
    @freezed
    abstract class VideoTime with _$VideoTime {
      /// [currentTime] ジャンプ先時刻
      /// [visible] 表示フラグ
      /// [fadeOut] フェードアウトフラグ
      factory VideoTime(double currentTime, bool visible,  bool fadeOut) = _VideoTime;
    }
    

    시간 표시의 상태를 변경하는 StateNotifier와 그 Provider를 작성합니다.



    video_time_state_notifier_provider.dart
    class VideoTimeStateNotifier extends StateNotifier<VideoTime> {
      VideoTimeStateNotifier() : super(VideoTime(0.0, false, false));
    
      /// 表示時刻を更新する
      /// [currentTime] 表示時刻
      void setTime(double currentTime) {
        state =
            state.copyWith(currentTime: currentTime, visible: true, fadeOut: false);
      }
    
      /// フェードアウトをリクエストする
      void requestFadeOut() {
        state = state.copyWith(fadeOut: true);
      }
    }
    
    final videoTimeStateNotifierProvider =
        StateNotifierProvider((_) => VideoTimeStateNotifier());
    
    

    재생 시간 변경이 발생하면 VideoTimeStateNotifier 클래스의 setTime 메서드가 호출됩니다.

    시각 표시의 Widget 빌드



    여기서 중요한 것은 useEffect 을 사용하는 것입니다. 그 이유는 다음 절에서 설명합니다.

    video_time_builder.dart
    HookBuilder makeVideoTimeBuilder() {
      return HookBuilder(builder: (context) {
        // 状態を取得
        final videoTime = useProvider(videoTimeStateNotifierProvider.state);
        // 不透明度
        double opacity = 1.0;
        // アニメーションのミリ秒
        int ms = 0;
        // フェードアウトするときは、0.3秒かけて不透明度0に向かう。
        if (videoTime.fadeOut) {
          opacity = 0.0;
          ms = 300;
        }
        // 0.7後にフェードアウトをリクエスト
        if (videoTime.visible && !videoTime.fadeOut) {
          // 重要
          useEffect(() {
            Timer timer = Timer(Duration(milliseconds: 700), () {
              // 0.7秒後に実行されるブロック
              // フェードアウトをリクエスト
              final videoTimeStateNotifier =
              context.read(videoTimeStateNotifierProvider);
              videoTimeStateNotifier.requestFadeOut();
            });
            return () {
              // このウィジットが破棄されたときに呼ばれる
              timer.cancel();
            };
          });
        }
        final textStyle = TextStyle(fontSize: 34, color: Colors.white);
        return Visibility(
          visible: videoTime.visible,
          child: Center(
              child: AnimatedOpacity(
            opacity: opacity,
            duration: Duration(milliseconds: ms),
            child: Container(
              decoration: ShapeDecoration(
                  color: Color.fromARGB(0xbb, 0, 0, 0), shape: StadiumBorder()),
              padding: EdgeInsets.symmetric(vertical: 16, horizontal: 32),
              child: Text(toTimeString(videoTime.currentTime), style: textStyle),
            ),
          )),
        );
      });
    }
    

    useEffect를 사용하는 이유



    실은 이 앱, 브라우저 가로폭에 따라 레이아웃이 다릅니다. 동영상 플레이어와는 별도로 이미지 인식으로 추출된 장면 목록이 있으며, 가로 폭이 좁을 때는 아래로, 넓을 때는 오른쪽에 배치됩니다.



    그리고, 그 위젯 배치의 변경이 행해질 때, 위젯이 파기됩니다만, 파기된 후에 0.7초 후의 콜백이 실행되면, 다음과 같은 에러가 발생합니다.
    Error: Looking up a deactivated widget's ancestor is unsafe.
    At this point the state of the widget's element tree is no longer stable.
    To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
    

    따라서 위젯이 파기되면 타이머를 취소하여 0.7초 후 콜백이 호출되지 않도록 합니다. useEffect 에 건네주는 람다의 반환값은 파기된 타이밍에 불리는 람다가 됩니다.

    감상



    나는 평소 안드로이드 네이티브로 앱을 개발하고 있어, 이러한 애니메이션은 ObjectAnimator 클래스의 setStartDelay 메소드로 실시합니다만, Flutter는 방법이 다르다고 생각했습니다.

    좋은 웹페이지 즐겨찾기