관리 메뉴

Bull

[Flutter::Animation] implicits animations (ImplicitlyAnimatedWidget) 본문

Software Framework/Flutter

[Flutter::Animation] implicits animations (ImplicitlyAnimatedWidget)

Bull_ 2024. 7. 24. 07:29

implicts animations란?

implicits animations은 암시적 애니메이션으로 그대로 해석해보면 애니메이션을 암시해줍니다. 그 말은 애니메이션을 넌지시 알려준다는 의미가 되겠네요. 개념을 쉽게 이해하기 위해서 이 위젯을 사용하면 어떤 동작이 일어나면 애니메이션을 진행시켜줘 라고 생각해도 무방할 것 같습니다.

공식 문서의 정의된 내용을 보면 "암시적 애니메이션을 사용하면 위젯 속성을 애니메이션화할 수 있으며, 대상 값이 변경될 때마다 위젯이 이전 값에서 새 값으로 속성을 애니메이션화합니다. 이러한 방식으로 암시적 애니메이션은 편리하게 제어권을 교환하여 애니메이션 효과를 관리합니다." 라고 나와 있습니다. 즉 컨트롤러에 의해 관리될 필요없이 제한적인 상황에서 특정 프로퍼티의 변화에만 반응하는 애니메이션을 만들고 싶으면 이 implicits animations을 동작해주는 Widget을 사용하면 한결 더 간결하고 간편한 코드로 관리할 수 있겠습니다.

종류

ImplicitlyAnimatedWidget의 공식 문서를 참고해보면 어떤 ImplicitlyAnimatedWidget의 나오는 지 확인할 수 있는데요, 하나하나 살펴보겠습니다.

참고로 ImplicitlyAnimatedWidget는 실제 Widget의 Class 명칭을 나타내고 implicits animations는 개념을 나타내는 용어이니 혼동에 유의바랍니다.

TweenAnimationBuilder: Tween의 변화에 따른 애니메이션을 변화시켜줍니다.
AnimatedAlign: Align(정렬) 프로퍼티의 변화에 따른 애니메이션을 변화시켜줍니다.
AnimatedContainer: Container의 여러 프로퍼티(색상, 크기, 정렬 등)의 변화에 따른 애니메이션을 적용합니다. 다중 프로퍼티에 반응하고 싶은 게 목적일 때 사용합니다.
AnimatedDefaultTextStyle: TextStyle(텍스트 스타일)의 변화에 따른 애니메이션을 적용합니다.
AnimatedScale: Transform.scale(크기 조정)의 변화에 따른 애니메이션을 적용합니다.
AnimatedRotation: Transform.rotate(회전)의 변화에 따른 애니메이션을 적용합니다.
AnimatedSlide: 위젯의 위치를 정상 위치에 상대적으로 애니메이션합니다.
AnimatedOpacity: Opacity(불투명도)의 변화에 따른 애니메이션을 적용합니다.
AnimatedPadding: Padding(패딩)의 변화에 따른 애니메이션을 적용합니다.
AnimatedPhysicalModel: PhysicalModel(물리적 모델)의 변화에 따른 애니메이션을 적용합니다.
AnimatedPositioned: Positioned(위치)의 변화에 따른 애니메이션을 적용합니다.
AnimatedPositionedDirectional: PositionedDirectional(위치, 방향성)의 변화에 따른 애니메이션을 적용합니다.
AnimatedTheme: Theme(테마)의 변화에 따른 애니메이션을 적용합니다.
AnimatedCrossFade: 두 자식 위젯 사이를 크로스 페이드하며 애니메이션합니다.
AnimatedSize: 주어진 기간 동안 크기 변화를 애니메이션합니다.
AnimatedSwitcher: 하나의 위젯에서 다른 위젯으로 페이드 전환하며 애니메이션합니다.

예제

그냥 보면 모를 수도 있으니 예제를 스윽 보면서 이해해보겠습니다.

TweenAnimationBuilder

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('TweenAnimationBuilder Example')),
        body: Center(child: TweenAnimationBuilderDemo()),
      ),
    );
  }
}

class TweenAnimationBuilderDemo extends StatefulWidget {
  @override
  _TweenAnimationBuilderDemoState createState() => _TweenAnimationBuilderDemoState();
}

class _TweenAnimationBuilderDemoState extends State<TweenAnimationBuilderDemo> {
  bool _isRed = true;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isRed = !_isRed;
        });
      },
      child: TweenAnimationBuilder(
        tween: ColorTween(begin: Colors.red, end: _isRed ? Colors.red : Colors.blue),
        duration: Duration(seconds: 1),
        builder: (context, Color? color, child) {
          return ColorFiltered(
            colorFilter: ColorFilter.mode(color!, BlendMode.modulate),
            child: Container(
              width: 100,
              height: 100,
              color: color,
            ),
          );
        },
      ),
    );
  }
}

TweenAnimationBuilder

setState를 통해서 _isRed의 값 상태를 관리합니다. tween프로퍼티는 _isRed의 상태에 따른 begin과 end의 값을 정해줍니다. 이로 인해 TweenAnimationBuildertween을 관측하기 때문에 tween의 프로퍼티가 변경됨에 따라 애니메이션의 동작을 나타낼 수 있게 됩니다.

AnimatedAlign

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedAlign Example')),
        body: Center(child: AnimatedAlignDemo()),
      ),
    );
  }
}

class AnimatedAlignDemo extends StatefulWidget {
  @override
  _AnimatedAlignDemoState createState() => _AnimatedAlignDemoState();
}

class _AnimatedAlignDemoState extends State<AnimatedAlignDemo> {
  bool _isTopLeft = true;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isTopLeft = !_isTopLeft;
        });
      },
      child: Container(
        width: 250,
        height: 250,
        color: Colors.blue[50],
        child: AnimatedAlign(
          alignment: _isTopLeft ? Alignment.topLeft : Alignment.bottomRight,
          duration: Duration(seconds: 1),
          curve: Curves.easeInOut,
          child: FlutterLogo(size: 50),
        ),
      ),
    );
  }
}

AnimatedAlign

setState를 통해 _isTopLeft의 상태를 관리합니다. alignment 프로퍼티의 _isTopLeft 상태에 따른 정렬을 표현하고 GestureDetector를 통해 top-left에 위치하다가 클릭하면 bottom-right로 정렬됩니다. AnimatedAlignalignment을 관측하기 때문에 alignment의 프로퍼티가 변경됨에 따라 애니메이션의 동작을 나타낼 수 있게 됩니다. 애니메이션이 없다면 어떤 효과도 없이 바로 바뀌게 되겠지만 애니메이션의 curve를 통해 부드럽게 프레임단위로 위치가 변하는 것에 차이를 느낄 수 있습니다.

AnimatedContainer

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedContainer Example')),
        body: Center(child: AnimatedContainerDemo()),
      ),
    );
  }
}

class AnimatedContainerDemo extends StatefulWidget {
  @override
  _AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isExpanded = !_isExpanded;
        });
      },
      child: AnimatedContainer(
        width: _isExpanded ? 200.0 : 100.0,
        height: _isExpanded ? 200.0 : 100.0,
        color: _isExpanded ? Colors.blue : Colors.red,
        alignment: _isExpanded ? Alignment.center : AlignmentDirectional.topCenter,
        duration: Duration(seconds: 1),
        curve: Curves.easeInOut,
        child: FlutterLogo(size: 75),
      ),
    );
  }
}

AnimatedContainer

setState를 통해 _isExpanded 상태를 관리합니다. width, height, color, alignment 프로퍼티는 _isExpanded 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _isExpanded의 값을 바꿉니다. AnimatedContainer는 프로퍼티 하나에 대해서만 애니메이션 효과를 주는 것이 아닌 여러 프로퍼티에 대해 애니메이션 효과를 주고 싶을 때 사용합니다. 참고로 결과에서 alignment가 안바뀌는 거 같지만 자세히 보면 마우스 포인터 기준으로 움직이는 걸 볼 수 있고 큰 컨테이너에 대한 center와 작은 컨테이너에 대한 top-center의 위치가 비슷하기 때문에 헷갈릴 수 있는 점 참고 바랍니다.

AnimatedDefaultTextStyle

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedDefaultTextStyle Example')),
        body: Center(child: AnimatedDefaultTextStyleDemo()),
      ),
    );
  }
}

class AnimatedDefaultTextStyleDemo extends StatefulWidget {
  @override
  _AnimatedDefaultTextStyleDemoState createState() => _AnimatedDefaultTextStyleDemoState();
}

class _AnimatedDefaultTextStyleDemoState extends State<AnimatedDefaultTextStyleDemo> {
  bool _toggled = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _toggled = !_toggled;
        });
      },
      child: AnimatedDefaultTextStyle(
        style: _toggled
            ? TextStyle(fontSize: 40.0, color: Colors.blue, fontWeight: FontWeight.bold)
            : TextStyle(fontSize: 20.0, color: Colors.red, fontWeight: FontWeight.normal),
        duration: Duration(seconds: 1),
        child: Text('Hello Flutter'),
      ),
    );
  }
}

AnimatedDefaultTextStyle

setState를 통해 _toggled 상태를 관리합니다. TextStyle 위젯은 _toggled 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _toggled 값을 바꿉니다. AnimatedDefaultTextStyle은 위의 다른 애니메이티드 위젯처럼 TextStyle 위젯의 변화에 애니메이션 효과를 추가해줍니다.

AnimatedScale

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedScale Example')),
        body: Center(child: AnimatedScaleDemo()),
      ),
    );
  }
}

class AnimatedScaleDemo extends StatefulWidget {
  @override
  _AnimatedScaleDemoState createState() => _AnimatedScaleDemoState();
}

class _AnimatedScaleDemoState extends State<AnimatedScaleDemo> {
  bool _scaled = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _scaled = !_scaled;
        });
      },
      child: AnimatedScale(
        scale: _scaled ? 2.0 : 1.0,
        duration: Duration(seconds: 1),
        child: FlutterLogo(size: 100),
      ),
    );
  }
}

AnimatedScale

setState를 통해 _scaled 상태를 관리합니다. scale 프로퍼티는 _scaled 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _scaled 값을 바꿉니다. AnimatedScale은 위의 다른 애니메이티드 위젯처럼 scale 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다.

AnimatedRotation

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedRotation Example')),
        body: Center(child: AnimatedRotationDemo()),
      ),
    );
  }
}

class AnimatedRotationDemo extends StatefulWidget {
  @override
  _AnimatedRotationDemoState createState() => _AnimatedRotationDemoState();
}

class _AnimatedRotationDemoState extends State<AnimatedRotationDemo> {
  bool _rotated = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _rotated = !_rotated;
        });
      },
      child: AnimatedRotation(
        turns: _rotated ? 1.0 : 0.0,
        duration: Duration(seconds: 1),
        child: FlutterLogo(size: 100),
      ),
    );
  }
}

AnimatedRotation

setState를 통해 _rotated 상태를 관리합니다. scale 프로퍼티는 _rotated 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _rotated 값을 바꿉니다. AnimatedRotation은 위의 다른 애니메이티드 위젯처럼 turns 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다.

AnimatedSlide

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedSlide Example')),
        body: Center(child: AnimatedSlideDemo()),
      ),
    );
  }
}

class AnimatedSlideDemo extends StatefulWidget {
  @override
  _AnimatedSlideDemoState createState() => _AnimatedSlideDemoState();
}

class _AnimatedSlideDemoState extends State<AnimatedSlideDemo> {
  bool _slided = false;

  void _toggleSlide() {
    setState(() {
      _slided = !_slided;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedSlide(
          offset: _slided ? Offset(1.0, 0.0) : Offset(0.0, 0.0),
          duration: Duration(seconds: 1),
          curve: Curves.easeInOut,
          child: Container(
            width: 100,
            height: 100,
            child: Center(
              child: FlutterLogo(size: 100),
            ),
          ),
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: _toggleSlide,
          child: Text('Toggle Slide'),
        ),
      ],
    );
  }
}

AnimatedSlide

setState를 통해 _slided 상태를 관리합니다. offset 프로퍼티는 _slided 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _slided 값을 바꿉니다. AnimatedSlide은 위의 다른 애니메이티드 위젯처럼 offset 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다.
참고할 사항은 이 위젯에서는 버튼을 사용했습니다. 로고를 누르면 애니메이션이 끝나고 다시 누를 때 동작하지 않아서 원인을 알아보다가 결국 찾지 못하며 다른 방법으로 바꾼 것입니다.

AnimatedOpacity

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedOpacity Example')),
        body: Center(child: AnimatedOpacityDemo()),
      ),
    );
  }
}

class AnimatedOpacityDemo extends StatefulWidget {
  @override
  _AnimatedOpacityDemoState createState() => _AnimatedOpacityDemoState();
}

class _AnimatedOpacityDemoState extends State<AnimatedOpacityDemo> {
  bool _isVisible = true;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isVisible = !_isVisible;
        });
      },
      child: AnimatedOpacity(
        opacity: _isVisible ? 1.0 : 0.0,
        duration: Duration(seconds: 1),
        child: FlutterLogo(size: 100),
      ),
    );
  }
}

AnimatedOpacity

setState를 통해 _isVisible 상태를 관리합니다. opacity 프로퍼티는 _isVisible 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _isVisible의 값을 바꿉니다. AnimatedOpacity는 위의 다른 애니메이티드 위젯처럼 opacity프로퍼티 변화에 애니메이션 효과를 추가해줍니다.

AnimatedPadding

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedPadding Example')),
        body: Center(child: AnimatedPaddingDemo()),
      ),
    );
  }
}

class AnimatedPaddingDemo extends StatefulWidget {
  @override
  _AnimatedPaddingDemoState createState() => _AnimatedPaddingDemoState();
}

class _AnimatedPaddingDemoState extends State<AnimatedPaddingDemo> {
  bool _isPadded = false;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      width: 200,
      height: 200,
      child: GestureDetector(
        onTap: () {
          setState(() {
            _isPadded = !_isPadded;
          });
        },
        child: AnimatedPadding(
          padding: _isPadded ? EdgeInsets.all(50.0) : EdgeInsets.all(10.0),
          duration: Duration(milliseconds: 100),
          curve: Curves.easeInOut,
          child: Container(
            child: FlutterLogo(size: 100),
          ),
        ),
      ),
    );
  }
}

AnimatedPadding

setState를 통해 _isPadded 상태를 관리합니다. padding 프로퍼티는 _isPadded 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _isPadded 값을 바꿉니다. AnimatedPadding는 위의 다른 애니메이티드 위젯과 차이가 있진 않지만 예제에서는 부모 위젯으로 크기가 정해진 Container를 추가해주었는데 부모 위젯이 없으면 AnimatedPadding의 자식 컨테이너는 크기가 자동으로 정해져서 padding 프로퍼티가 작동하지 않습니다.

AnimatedPhysicalModel

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedPhysicalModel Example')),
        body: Center(child: AnimatedPhysicalModelDemo()),
      ),
    );
  }
}

class AnimatedPhysicalModelDemo extends StatefulWidget {
  @override
  _AnimatedPhysicalModelDemoState createState() =>
      _AnimatedPhysicalModelDemoState();
}

class _AnimatedPhysicalModelDemoState extends State<AnimatedPhysicalModelDemo> {
  bool _elevated = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _elevated = !_elevated;
        });
      },
      child: AnimatedPhysicalModel(
        shape: BoxShape.rectangle,
        elevation: _elevated ? 100.0 : 0.0,
        color: Colors.blue,
        shadowColor: Colors.black,
        duration: Duration(seconds: 1),
        child: Container(
          width: 100,
          height: 100,
          child: FlutterLogo(size: 75),
        ),
      ),
    );
  }
}

AnimatedPhysicalModel

setState를 통해 _elevated 상태를 관리합니다. elevation 프로퍼티는 _elevated 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _elevated 값을 바꿉니다. AnimatedPhysicalModel은 위의 다른 애니메이티드 위젯처럼 elevation 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다. 참고로 borderRadius값도 관측한다는 점 알아두시기 바랍니다.

AnimatedPositioned

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedPositioned Example')),
        body: Center(child: AnimatedPositionedDemo()),
      ),
    );
  }
}

class AnimatedPositionedDemo extends StatefulWidget {
  @override
  _AnimatedPositionedDemoState createState() => _AnimatedPositionedDemoState();
}

class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
  bool _moved = false;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AnimatedPositioned(
          width: 100.0,
          height: 100.0,
          top: _moved ? 200.0 : 50.0,
          left: _moved ? 200.0 : 50.0,
          duration: Duration(seconds: 1),
          curve: Curves.easeInOut,
          child: GestureDetector(
            onTap: () {
              setState(() {
                _moved = !_moved;
              });
            },
            child: Container(
              color: Colors.blue,
              child: FlutterLogo(size: 100),
            ),
          ),
        ),
      ],
    );
  }
}

AnimatedPositioned

setState를 통해 _moved 상태를 관리합니다. top,left 프로퍼티는 _moved 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _moved 값을 바꿉니다. AnimatedPositioned은 위의 다른 애니메이티드 위젯처럼 top,left 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다. 위젯의 이름에서 알 수 있듯이 top, left만 아니라 right, bottom에도 반응합니다. 그 외 position과 관련된 프로퍼티에 대해서도 결과는 같습니다.

AnimatedPositionedDirectional

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedPositionedDirectional Example')),
        body: Center(child: AnimatedPositionedDirectionalDemo()),
      ),
    );
  }
}

class AnimatedPositionedDirectionalDemo extends StatefulWidget {
  @override
  _AnimatedPositionedDirectionalDemoState createState() => _AnimatedPositionedDirectionalDemoState();
}

class _AnimatedPositionedDirectionalDemoState extends State<AnimatedPositionedDirectionalDemo> {
  bool _moved = false;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AnimatedPositionedDirectional(
          width: 100.0,
          height: 100.0,
          top: _moved ? 200.0 : 50.0,
          start: _moved ? 200.0 : 50.0,
          duration: Duration(seconds: 1),
          curve: Curves.easeInOut,
          child: GestureDetector(
            onTap: () {
              setState(() {
                _moved = !_moved;
              });
            },
            child: Container(
              color: Colors.blue,
              child: FlutterLogo(size: 100),
            ),
          ),
        ),
      ],
    );
  }
}

AnimatedPositionedDirectional

setState를 통해 _moved 상태를 관리합니다. top,start 프로퍼티는 _moved 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _moved 값을 바꿉니다. AnimatedPositionedDirectional은 위의 다른 애니메이티드 위젯처럼 top,start 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다. AnimatedPositionedDirectional과는 비슷하지만 차이점은 '방향성'이 추가되었습니다. 즉 start프로퍼티에 대한 관측이 추가 되었습니다.

AnimatedTheme

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: AnimatedThemeDemo(),
    );
  }
}

class AnimatedThemeDemo extends StatefulWidget {
  @override
  _AnimatedThemeDemoState createState() => _AnimatedThemeDemoState();
}

class _AnimatedThemeDemoState extends State<AnimatedThemeDemo> {
  bool _isDark = false;

  void _toggleTheme() {
    setState(() {
      _isDark = !_isDark;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedTheme(
      data: _isDark ? ThemeData.dark() : ThemeData.light(),
      duration: Duration(seconds: 1),
      child: Scaffold(
        appBar: AppBar(title: Text('AnimatedTheme Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                onPressed: _toggleTheme,
                child: Text('Toggle Theme'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

AnimatedTheme

setState를 통해 _isDark 상태를 관리합니다. data 프로퍼티는 _isDark 상태에 변화에 따라 값을 표현합니다. GestureDetector를 통해 _isDark 값을 바꿉니다. AnimatedTheme은 위의 다른 애니메이티드 위젯처럼 data 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다. data 프로퍼티에는 ThemeData class를 반환하면 됩니다. 앱에서 테마색을 변경할 때 사용하면 유용할 것 같습니다.

AnimatedCrossFade

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedCrossFade Example')),
        body: Center(child: AnimatedCrossFadeDemo()),
      ),
    );
  }
}

class AnimatedCrossFadeDemo extends StatefulWidget {
  @override
  _AnimatedCrossFadeDemoState createState() => _AnimatedCrossFadeDemoState();
}

class _AnimatedCrossFadeDemoState extends State<AnimatedCrossFadeDemo> {
  bool _first = true;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _first = !_first;
        });
      },
      child: AnimatedCrossFade(
        firstChild: Container(
          width: 100,
          height: 100,
          color: Colors.blue,
          child: Center(child: Text('First')),
        ),
        secondChild: Container(
          width: 100,
          height: 100,
          color: Colors.red,
          child: Center(child: Text('Second')),
        ),
        crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
        duration: Duration(seconds: 1),
      ),
    );
  }
}

AnimatedCrossFade

setState를 통해 _first 상태를 관리합니다. crossFadeState 프로퍼티는 _first 상태에 참, 거짓에 따라 firstChild 프로퍼티에 있는 자식 위젯을 반환할 것인지, secondChild 프로퍼티에 있는 자식 위젯을 반환할 것인지 나타낼 수 있습니다. GestureDetector를 통해 _first 값을 바꿉니다. AnimatedCrossFade의 특징은 이름에 걸맞게 애니메이션 효과를 부여하면서 두 자식 위젯이 교차하며 사라집니다.

AnimatedSize

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedSize Example')),
        body: Center(child: AnimatedSizeDemo()),
      ),
    );
  }
}

class AnimatedSizeDemo extends StatefulWidget {
  @override
  _AnimatedSizeDemoState createState() => _AnimatedSizeDemoState();
}

class _AnimatedSizeDemoState extends State<AnimatedSizeDemo>
    with SingleTickerProviderStateMixin {
  bool _large = false;



  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _large = !_large;
        });
      },
      child: Center(
        child: AnimatedSize(
          duration: Duration(seconds: 1),
          curve: Curves.easeInOut,
          child: Container(
            alignment: Alignment.center,
            child: FlutterLogo(size: _large ? 200.0 : 100.0),
          ),
        ),
      ),
    );
  }
}

AnimatedSize

setState를 통해 _large 상태를 관리합니다. size 프로퍼티는 _large 상태에 변화에 따라 값을 표현합니다.

GestureDetector를 통해 _large 값을 바꿉니다. AnimatedSize은 위의 다른 애니메이티드 위젯처럼 size 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다.

AnimatedSwitcher

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AnimatedSwitcher Example')),
        body: Center(child: AnimatedSwitcherDemo()),
      ),
    );
  }
}

class AnimatedSwitcherDemo extends StatefulWidget {
  @override
  _AnimatedSwitcherDemoState createState() => _AnimatedSwitcherDemoState();
}

class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
  bool _toggled = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _toggled = !_toggled;
        });
      },
      child: AnimatedSwitcher(
        duration: Duration(seconds: 1),
        child: _toggled
            ? Container(
                key: ValueKey(1),
                width: 100,
                height: 100,
                color: Colors.blue,
                child: Center(child: Text('First')),
              )
            : Container(
                key: ValueKey(2),
                width: 100,
                height: 100,
                color: Colors.red,
                child: Center(child: Text('Second')),
              ),
      ),
    );
  }
}

AnimateSwither

setState를 통해 _toggled 상태를 관리합니다. child 위젯으로 AnimatedCrossFade와 같은 방식을 구현할 수 있습니다. GestureDetector를 통해 _toggled 값을 바꿉니다. AnimateSwither은 위의 다른 애니메이티드 위젯처럼 size 프로퍼티의 변화에 애니메이션 효과를 추가해줍니다.

참고 자료

Dart 공식 문서 - ImplicitlyAnimatedWidget