Skip to content

flutter-preview/Jinwook-Song-flutter_animations

 
 

Repository files navigation

Flutter animations

프로젝트 기간 23.05.17
프로젝트 목적 flutter로 다양한 애니메이션 구현
Github https://github.com/Jinwook-Song/flutter_animations

Animation Decision Tree

Untitled


  • Implicit Animations 애니메이션에 대해서 코드를 작성할 필요가 없다. 플러터가 알아서 다 해준다. Animated로 시작하는 위젯이 이에 해당된다. (dcos)

    • AnimatedContainer: 어떤것이든 transition 효과를 준다
    AnimatedContainer(
                  duration: const Duration(milliseconds: 300),
                  width: size.width * 0.4,
                  height: size.width * 0.4,
                  transform: Matrix4.rotationZ(_visible ? 1 / 2 * pi : 0),
                  transformAlignment: Alignment.center,
                  decoration: BoxDecoration(
                    color: _visible ? Colors.pink : Colors.amber,
                    borderRadius: BorderRadius.circular(_visible ? 10 : 100),
                  ),
                ),
    • Curves (docs)
    • TweenAnimationBuilder 나만의 implicit animation 위젯을 만들 수 있다 내장된 AnimatedWidget이 없는 경우 사용할 수 있다 tween: from ~ to 사이의 값으로 transition 효과를 줌 value: currently animated value
      TweenAnimationBuilder(
                    tween: ColorTween(
                      begin: Colors.amber,
                      end: Colors.purple,
                    ),
                    curve: Curves.bounceInOut,
                    duration: const Duration(seconds: 5),
                    builder: (context, value, child) {
                      return Image.network(
                        'https://upload.wikimedia.org/wikipedia/commons/4/4f/Dash%2C_the_mascot_of_the_Dart_programming_language.png',
                        color: value,
                        colorBlendMode: BlendMode.hue,
                      );
                    },
                  ),
  • Explicit Animations 여러 위젯을 animation 하고싶은 경우, animation에 더 많은 통제를 하고싶은 경우(loop, pause, reverse, etc…)

    • SingleTickerProviderStateMixin Ticker: call its callback once per animation frame SingleTickerProviderStateMixin: only tick while the current tree is enabled → 빠른 rebuild를 위해 Ticker를 사용하고, enable 상태에서만 활성화하도록
    import 'package:flutter/material.dart';
    
       class ExplicitAnimationsScreen extends StatefulWidget {
         const ExplicitAnimationsScreen({super.key});
    
         @override
         State<ExplicitAnimationsScreen> createState() =>
             _ExplicitAnimationsScreenState();
       }
    
       class _ExplicitAnimationsScreenState extends State<ExplicitAnimationsScreen>
           with SingleTickerProviderStateMixin {
         late final AnimationController _animationController = AnimationController(
           vsync: this,
           duration: const Duration(
             seconds: 10,
           ),
           lowerBound: 50.0,
           upperBound: 100.0,
         )
           // Called when the animation value changes
           // 하지만 전체를 rebuild하기 때문에 매우 부적합하다
           ..addListener(() {
             setState(() {});
           });
    
         void _play() {
           _animationController.forward();
         }
    
         void _pause() {
           _animationController.stop();
         }
    
         void _rewind() {
           _animationController.reverse();
         }
    
         @override
         void initState() {
           super.initState();
         }
    
         @override
         Widget build(BuildContext context) {
           return Scaffold(
             appBar: AppBar(
               title: const Text('Explicit Animations'),
             ),
             body: Center(
               child: Column(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
                   Text(
                     _animationController.value.toStringAsFixed(3),
                     style: const TextStyle(fontSize: 40),
                   ),
                   const SizedBox(
                     height: 20,
                   ),
                   Row(
                     mainAxisAlignment: MainAxisAlignment.center,
                     children: [
                       ElevatedButton(
                         onPressed: _play,
                         child: const Text(
                           'Play',
                         ),
                       ),
                       ElevatedButton(
                         onPressed: _pause,
                         child: const Text(
                           'Pause',
                         ),
                       ),
                       ElevatedButton(
                         onPressed: _rewind,
                         child: const Text(
                           'Rewind',
                         ),
                       ),
                     ],
                   )
                 ],
               ),
             ),
           );
         }
       }
    • AnimatedBuilder animation이 바뀌는 부분만 새롭게 render

      AnimatedBuilder(
                         animation: _animationController,
                         builder: (context, child) {
                           return Text(
                             _animationController.value.toStringAsFixed(3),
                             style: const TextStyle(fontSize: 40),
                           );
                         },
                       ),
    • Explicit Animations

      // Connect Tween and AnimationController
           late final Animation<Decoration> _decorationAnimation = DecorationTween(
             begin: BoxDecoration(
               color: Colors.amber,
               borderRadius: BorderRadius.circular(120),
             ),
             end: BoxDecoration(
               color: Colors.purple,
               borderRadius: BorderRadius.circular(20),
             ),
           ).animate(_animationController);
      
           late final Animation<double> _rotationAnimation = Tween(
             begin: 0.0,
             end: 1 / 8,
           ).animate(_animationController);
      
           late final Animation<double> _scaleAnimation = Tween(
             begin: 1.0,
             end: 0.5,
           ).animate(_animationController);
      
           late final Animation<Offset> _offsetAnimation = Tween(
             begin: Offset.zero,
             end: const Offset(0, -1),
           ).animate(_animationController);
      
         SlideTransition(
                       position: _offsetAnimation,
                       child: ScaleTransition(
                         scale: _scaleAnimation,
                         child: RotationTransition(
                           turns: _rotationAnimation,
                           child: DecoratedBoxTransition(
                             decoration: _decorationAnimation,
                             child: const SizedBox(
                               width: 200,
                               height: 200,
                             ),
                           ),
                         ),
                       ),
                     ),
    • Curve

      late final AnimationController _animationController = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 2),
          reverseDuration: const Duration(seconds: 1),
        );
        late final CurvedAnimation _curvedAnimation = CurvedAnimation(
          parent: _animationController,
          curve: Curves.elasticOut,
          reverseCurve: Curves.elasticIn,
        );
    • ValueNotifier & ValueListenableBuilder animation 진행 상황을 render할때, setState를 이용하지 않고, ValueNotifier의 값을 변경하고, 이 변화된 값을 렌더해주는 ValueListenableBuilder를 이용하여 최적화 할 수 있다

      late final AnimationController _animationController = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 2),
          reverseDuration: const Duration(seconds: 1),
        )..addListener(() {
            _progress.value = _animationController.value;
          });
      
      final ValueNotifier<double> _progress = ValueNotifier(0.0);
      
        void _onChanged(double value) {
          _progress.value = value;
          _animationController.value = value;
        }
      
      ValueListenableBuilder(
                    valueListenable: _progress,
                    builder: (context, value, child) {
                      return Slider(
                        value: value,
                        onChanged: _onChanged,
                      );
                    },
                  )
    • AnimationStatus foward, completed, reverse, dismissed 에 따라 animation을 컨트롤 할 수 있다

      late final AnimationController _animationController = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 2),
          reverseDuration: const Duration(seconds: 1),
        )
          ..addListener(() {
            _progress.value = _animationController.value;
          })
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              _rewind();
            } else if (status == AnimationStatus.dismissed) {
              _play();
            }
          });
      
      bool _looping = false;
      
        void _toggleLooping() {
          if (_looping) {
            _animationController.stop();
          } else {
            _animationController.repeat(reverse: true);
          }
          setState(() {
            _looping = !_looping;
          });
        }
  • Apple Watch animate custom painter

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    class AppleWatchScreen extends StatefulWidget {
      const AppleWatchScreen({super.key});
    
      @override
      State<AppleWatchScreen> createState() => _AppleWatchScreenState();
    }
    
    class _AppleWatchScreenState extends State<AppleWatchScreen>
        with SingleTickerProviderStateMixin {
      late final AnimationController _animationController = AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 3000),
        lowerBound: 0.001 * pi,
        upperBound: 2.0 * pi,
      );
    
      void _animateValues() {
        _animationController.repeat();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.black,
          appBar: AppBar(
            backgroundColor: Colors.black,
            foregroundColor: Colors.white,
            automaticallyImplyLeading: false,
            leading: IconButton(
              onPressed: () => Navigator.of(context).pop(),
              icon: const Icon(
                Icons.arrow_back_ios_new,
                color: Colors.white,
              ),
            ),
            title: const Text('Apple Watch'),
          ),
          body: Center(
            child: AnimatedBuilder(
              animation: _animationController,
              builder: (context, child) {
                return CustomPaint(
                  painter: AppleWatchPainter(
                    progress: _animationController.value,
                  ),
                  size: const Size(400, 400),
                );
              },
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _animateValues,
            child: const Icon(Icons.refresh),
          ),
        );
      }
    }
    
    class AppleWatchPainter extends CustomPainter {
      final double progress;
    
      AppleWatchPainter({required this.progress});
      @override
      void paint(Canvas canvas, Size size) {
        final raidus = size.width / 2;
        final redRadius = raidus * 0.9;
        final greenRadius = raidus * 0.65;
        final blueRadius = raidus * 0.4;
        const double strokeWidth = 45;
        const startingAngle = -1 / 2 * pi;
    
        final center = Offset(raidus, raidus);
    
        // Background Circls
        final redCirclePaint = Paint()
          ..color = Colors.pink.withOpacity(0.3)
          ..style = PaintingStyle.stroke
          ..strokeWidth = strokeWidth;
        canvas.drawCircle(center, redRadius, redCirclePaint);
    
        final greenCirclePaint = Paint()
          ..color = Colors.lightGreen.withOpacity(0.3)
          ..style = PaintingStyle.stroke
          ..strokeWidth = strokeWidth;
        canvas.drawCircle(center, greenRadius, greenCirclePaint);
    
        final blueCirclePaint = Paint()
          ..color = Colors.cyan.withOpacity(0.3)
          ..style = PaintingStyle.stroke
          ..strokeWidth = strokeWidth;
        canvas.drawCircle(center, blueRadius, blueCirclePaint);
    
        // Arc
        final redArcRect = Rect.fromCircle(center: center, radius: redRadius);
        final redArcPaint = Paint()
          ..color = Colors.pink
          ..style = PaintingStyle.stroke
          ..strokeWidth = 45
          ..strokeCap = StrokeCap.round;
        canvas.drawArc(
          redArcRect,
          startingAngle,
          progress,
          false,
          redArcPaint,
        );
    
        final greenArcRect = Rect.fromCircle(center: center, radius: greenRadius);
        final greenArcPaint = Paint()
          ..color = Colors.lightGreen
          ..style = PaintingStyle.stroke
          ..strokeWidth = 45
          ..strokeCap = StrokeCap.round;
        canvas.drawArc(
          greenArcRect,
          startingAngle,
          progress,
          false,
          greenArcPaint,
        );
    
        final blueArcRect = Rect.fromCircle(center: center, radius: blueRadius);
        final blueArcPaint = Paint()
          ..color = Colors.lightBlue
          ..style = PaintingStyle.stroke
          ..strokeWidth = 45
          ..strokeCap = StrokeCap.round;
        canvas.drawArc(
          blueArcRect,
          startingAngle,
          progress,
          false,
          blueArcPaint,
        );
      }
    
      @override
      bool shouldRepaint(covariant AppleWatchPainter oldDelegate) {
        return oldDelegate.progress != progress;
      }
    }
  • Swiping Cards

    import 'package:flutter/material.dart';
    
    class SwipingCardsScreen extends StatefulWidget {
      const SwipingCardsScreen({super.key});
    
      @override
      State<SwipingCardsScreen> createState() => _SwipingCardsScreenState();
    }
    
    class _SwipingCardsScreenState extends State<SwipingCardsScreen> {
      double _posX = 0;
    
      void _onHorizontalDragUpdate(DragUpdateDetails details) {
        setState(() {
          _posX += details.delta.dx;
        });
      }
    
      void _onHorizontalDragEnd(DragEndDetails details) {
        setState(() {
          _posX = 0;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        final size = MediaQuery.of(context).size;
        return Scaffold(
          appBar: AppBar(
            title: const Text(
              'Swiping Cards',
            ),
          ),
          body: Stack(children: [
            Align(
              alignment: Alignment.topCenter,
              child: GestureDetector(
                onHorizontalDragUpdate: _onHorizontalDragUpdate,
                onHorizontalDragEnd: _onHorizontalDragEnd,
                child: Transform.translate(
                  offset: Offset(_posX, 0),
                  child: Material(
                    elevation: 20,
                    color: Colors.red.shade100,
                    child: SizedBox(
                      width: size.width * 0.8,
                      height: size.height * 0.5,
                    ),
                  ),
                ),
              ),
            )
          ]),
        );
      }
    }
    • interpolation transform: Tween값을 lowerBound, uppderBound의 값의 범위로 바꿔준다.

      late final AnimationController _animationController = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 2000),
          lowerBound: -size.width,
          upperBound: size.width,
          value: 0,
        );
      
        late final Tween<double> _rotation = Tween(
          begin: -15,
          end: 15,
        );
      
      final angle = _rotation.transform(
                  (_animationController.value + size.width / 2) / size.width,
                );
  • Music Player

    • Album PageViewBuilder: viewportFraction: 0.8 값을 조정해 앞 뒤의 page도 보이도록
    import 'package:flutter/material.dart';
    
    class MusicPlaterScreen extends StatefulWidget {
      const MusicPlaterScreen({super.key});
    
      @override
      State<MusicPlaterScreen> createState() => _MusicPlaterScreenState();
    }
    
    class _MusicPlaterScreenState extends State<MusicPlaterScreen> {
      final PageController _pageController = PageController(
        initialPage: 0,
        viewportFraction: 0.8,
      );
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Stack(
            children: [
              PageView.builder(
                controller: _pageController,
                itemCount: 10,
                scrollDirection: Axis.horizontal,
                itemBuilder: (context, index) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Container(
                        height: 350,
                        decoration: BoxDecoration(
                          image: DecorationImage(
                            fit: BoxFit.cover,
                            image: AssetImage(
                              'assets/images/covers/yeonjae0$index.jpeg',
                            ),
                          ),
                        ),
                      ),
                      const SizedBox(height: 30),
                      const Text(
                        'yeonjae',
                        style: TextStyle(
                          fontSize: 26,
                        ),
                      ),
                      const SizedBox(height: 10),
                      const Text(
                        'home',
                        style: TextStyle(
                          fontSize: 18,
                        ),
                      ),
                    ],
                  );
                },
              )
            ],
          ),
        );
      }
    }
    • AnimatedSwitcher: Animate between old child and new child Container는 변경되지 않고 그 속성만 변하기 때문에 animation효과가 나타나지 않는다 따라서 key를 부여하여 다른 child임을 명시할 수 있다
      AnimatedSwitcher(
                  duration: const Duration(
                    milliseconds: 500,
                  ),
                  child: Container(
                    key: ValueKey(_currentPage),
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: AssetImage(
                          'assets/images/covers/yeonjae0$_currentPage.jpeg',
                        ),
                      ),
                    ),
                  ),
                ),
    • BackdropFilter
      child: BackdropFilter(
                      filter: ImageFilter.blur(
                        sigmaX: 15,
                        sigmaY: 15,
                      ),
                      child: Container(color: Colors.black.withOpacity(0.5)),
                    ),

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Dart 37.0%
  • C++ 29.8%
  • CMake 26.7%
  • HTML 2.6%
  • C 2.0%
  • Swift 1.6%
  • Other 0.3%