import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: animatedSwicher(),
    );
  }
}

class animatedSwicher extends StatefulWidget {
  const animatedSwicher({Key? key}) : super(key: key);

  @override
  _animatedSwicherState createState() => _animatedSwicherState();
}

class _animatedSwicherState extends State<animatedSwicher> {
  // 바뀔 위젯을 변수처리
  // var 타입은 값이 한번 정해지면 고정
  // var mWidget = FirstWidget();
  // dynamic 또는 부모타입인 widget
  Widget mWidget = FirstWidget();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedSwitcher(
              duration: Duration(seconds: 3),
              child: mWidget,
            ),
            RaisedButton(
              onPressed: () {
                setState(() {
                  mWidget = SecondWidget();
                });
              },
              child: Text("버튼"),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      width: 100,
      height: 100,
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      width: 100,
      height: 100,
    );
  }
}
728x90

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: customRotationTransition(),
    );
  }
}

class customRotationTransition extends StatefulWidget {
  const customRotationTransition({Key? key}) : super(key: key);

  @override
  _customRotationTransitionState createState() =>
      _customRotationTransitionState();
}

class _customRotationTransitionState extends State<customRotationTransition>
    with SingleTickerProviderStateMixin {
  double radian = 0.0;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // 지속적으로 무언가를 실행할땐 timer
    Timer.periodic(Duration(milliseconds: 100), (timer) {
      setState(() {
        radian = radian + pi / 9;
      });
      // setState가 재실행되기 때문에 랜더링이 계속 됨
    });
    // Future.delayed(Duration(seconds: 3),()=>{});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            RaisedButton(
              onPressed: () {
                setState(() {
                  radian = radian + pi / 2;
                });
              },
              child: Text("버튼"),
            ),
            Transform.rotate(
              // angle 만큼 회전
              // 단위 pi -> radian 개념 (반지름의 길이와 같은 원의 둘레)
              // radian이 되는 각도는 57도, 180도일때의 반지름과 3레디안의 차이는 0.14임
              // 원의 둘레는 radian으로 표현 => 원 둘레 = 5.x * radian
              // pi = 3 radian - 0.14
              // 원의 둘레 = pi * 2
              angle: radian,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
                child: Text("왼쪽모서리"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

참고

Timer를 통해 setState를 계속 호출하는건 현재 페이지의 랜더링을 계속하기 때문에 비효율적
버튼을 통해 애니메이션이 필요할때 위의 방법이 유용

728x90

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: positionedTransition(),
    );
  }
}

class positionedTransition extends StatefulWidget {
  const positionedTransition({Key? key}) : super(key: key);

  @override
  _positionedTransitionState createState() => _positionedTransitionState();
}

class _positionedTransitionState extends State<positionedTransition>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  @override
  void initState() {
    // TODO: implement initState
        // PositionedTransition의 컨트롤러를 컨트롤할 컨트롤러 
    _animationController =
        AnimationController(vsync: this, duration: Duration(seconds: 5));
    _animationController.repeat();
    super.initState();
    // 컨트롤러 초기화
    ;
  }

  @override
  Widget build(BuildContext context) {
        // 미디어쿼리로 디바이스 높이가져옴
    double height = MediaQuery.of(context).size.height;
        // PositionedTransition 컨트롤러 생성
    Animation<RelativeRect> _controller = RelativeRectTween(
            begin: RelativeRect.fromLTRB(0, height, 0, 0),
            end: RelativeRect.fromLTRB(0, 0, 0, 0))
        .animate(CurvedAnimation(
            parent: _animationController, curve: Curves.easeInOut));
    return Scaffold(
      body: Stack(
        children: [
                    // PositionedTransition은 스택에서만 작동
          PositionedTransition(
            rect: _controller,
            child: Container(color: Colors.blue),
          ),
        ],
      ),
    );
  }
}
728x90

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: scaleTransition(),
    );
  }
}

class scaleTransition extends StatefulWidget {
  const scaleTransition({Key? key}) : super(key: key);

  @override
  _scaleTransitionState createState() => _scaleTransitionState();
}

// vsync에 this를 넣기위해 tickerprovider를 mixin해줘야함
class _scaleTransitionState extends State<scaleTransition>
    with SingleTickerProviderStateMixin {
  // ScaleTransition의 controller
  late AnimationController _animationController;

  @override
  void initState() {
    // 컨트롤러 초기화
    _animationController = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    );
    // animation 형태
    _animationController.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // tween begin부터 end까지
    // animate 동안
    Animation<double> _animation =
        Tween(begin: 0.0, end: 1.0).animate(_animationController);

    return Scaffold(
      body: Center(
        child: ScaleTransition(
          // animation 구현할땐 Animation 객체가 필요
          scale: _animation,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

AnimationController 문서에 required로 TickerProvider타입의 vsync 필요

  AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }

TickerProvider 설명

하나의 애니메이션 : SingleTickerProviderStateMixin
여러 애니메이션 : TickerProviderStateMixin

/// An interface implemented by classes that can vend [Ticker] objects.
///
/// Tickers can be used by any object that wants to be notified whenever a frame
/// triggers, but are most commonly used indirectly via an
/// [AnimationController]. [AnimationController]s need a [TickerProvider] to
/// obtain their [Ticker]. If you are creating an [AnimationController] from a
/// [State], then you can use the [TickerProviderStateMixin] and
/// [SingleTickerProviderStateMixin] classes to obtain a suitable
/// [TickerProvider]. The widget test framework [WidgetTester] object can be
/// used as a ticker provider in the context of tests. In other contexts, you
/// will have to either pass a [TickerProvider] from a higher level (e.g.
/// indirectly from a [State] that mixes in [TickerProviderStateMixin]), or
/// create a custom [TickerProvider] subclass.

비슷한 애니메이션

FadeTransition

scale을 opacity로 변경, Tween을 0.0부터 1.0사이

728x90

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp()); // 비동기로 실행(이벤트 루프에 등록된다.)
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHome(),
    );
  }
}

class MyHome extends StatefulWidget {
  const MyHome({Key? key}) : super(key: key);

  @override
  _MyHomeState createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  late Size size;

  // 컨테이너 애니메이션
  bool isOpen = false;
  @override
  void initState() {
    super.initState();
    // error : context를 찾지 못함
    // size = MediaQuery.of(context).size;
  }

  @override
  Widget build(BuildContext context) {
    size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        actions: [
          // 아이콘을 버튼화
          InkWell(
            child: Icon(Icons.menu),
            onTap: () {
              setState(() {
                // 클릭할때마다 isOpen의 값이 반대로
                isOpen = !isOpen;
              });
            },
          ),
        ],
      ),
      body: Stack(
        children: [
          Center(
            child: Text("Animation screen"),
          ),
          AnimatedContainer(
            // 속도
            duration: Duration(seconds: 2),
            // animation 형태
            curve: Curves.easeInOut,
            height: double.infinity,
            // 컨테이너의 가로 사이즈
            width: size.width * (2 / 3),
            color: Colors.blue,
            // isOpen 값에 따라 가로 위치 이동
            transform: Matrix4.translationValues(
                isOpen ? size.width * (1 / 3) : size.width, 0, 0),
          )
        ],
      ),
    );
  }
}

728x90

controller가 singleton 방식인 것을 활용

기존

controller

class CountControllerWithGetX extends GetxController {
  int count = 0;
  void increase(String id) {
    count++;
    update([id]);
  }
}

contoller에 접근

Get.find<CountControllerWithGetX>().increase();

활용

controller에 state으로 get to 추가

class CountControllerWithGetX extends GetxController {
  static CountControllerWithGetX get to => Get.find();
  int count = 0;
  void increase(String id) {
    count++;
    update([id]);
  }
}

controller.to 로 접근 가능

CountControllerWithGetX.to.increase();

controller가 reactive 방식일때 stateless 대신 getview를 사용해서 접근

stateless -> GetView로 변경

// stateless -> GetView로 변경
class BindingPage extends GetView<CountControllerWithReactive> {
  const BindingPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Text(
            "GetX",
            style: TextStyle(fontSize: 50),
          ),
                    // reactive 방식이어야함
          Obx(() => Text(
                "${controller.count.value}",
                style: TextStyle(fontSize: 50),
              )),
          RaisedButton(
            onPressed: () {
              //GetView 안에 controller가 있음
              controller.increase();
            },
            child: Text(
              "+",
              style: TextStyle(fontSize: 50),
            ),
          ),
        ],
      ),
    );
  }
}

참고

https://www.youtube.com/watch?v=O1Bw-mwF9xc 

 

728x90

+ Recent posts