SingleGetTickerProviderMixin 를 mixin한 GetxController를 상속받은 컨트롤러 만들기

animation을 사용하기위해 SingleGetTickerProviderMixin를 가져옴

class TextAnimation extends GetxController with SingleGetTickerProviderMixin {
  Map<String, String?>? post;

  AnimationController? animationController;
  Animation<Offset>? animationOffset;

  @override
  void onInit() {
    super.onInit();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1000),
    );
    final curve =
        CurvedAnimation(parent: animationController!, curve: Curves.bounceIn);
    animationOffset = Tween<Offset>(
            begin: const Offset(0.0, -1.0), end: const Offset(0.0, 0.0))
        .animate(curve);
    animationController!.repeat();
    _loadData();
  }

  void _loadData() {
    post = Get.parameters;
  }
}

실제 사용

아래와 같이 statefull에서 사용하던 방법처럼 animation 부분에 controller에서 만든 animation을 가져오면 됨

class PostDetailView extends GetView<TextAnimation> {
  const PostDetailView();

  @override
  Widget build(BuildContext context) {
    Get.put(TextAnimation());
    return Scaffold(
      appBar: AppBar(
        title: Text(controller.post!['title']!),
      ),
      body: Column(
        children: [
          Hero(
              tag: controller.post!['uid']!,
              child: Image.asset(controller.post!['thumbnail']!)),
          Column(
            children: [
            // 아래와 같이 controller에서 애니메이션을 가져오면 됨
              SlideTransition(
                position: controller.animationOffset!,
                child: Text(
                  controller.post!['title']!,
                ),
              ),
              Text(
                controller.post!['description']!,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
728x90

2021.07.01 - [Usage/Flutter] - [Flutter] Json 데이터를 GetX를 활용하여 list view, detail view 만들기

 

[Flutter] Json 데이터를 GetX를 활용하여 list view, detail view 만들기

Json 데이터 예시 [ { "uid" : 1, "thumbnail": "assets/images/1.jpg", "title" : "꼬북좌 이미지1", "description" : "남심 '저격'브레이브걸스 유정 근황" }, { "uid" : 2, "thumbnail": "assets/images/2.jpg"..

mugon-devlog.tistory.com

Hero 애니메이션 적용

Hero 애니메이션을 적용하기 위해서는 적용받는 대상의 부모와 자식 위젯의 태그를 동일하게 만들어줘야함

아래는 디테일 페이지로 이동하기위해 클릭하는 위젯

아래 위젯의 Image를 클릭하면 디테일 페이지로 이동하는데 이때의 이미지와 디테일 페이지의 이미지의 태그를 동일하게 만들어줘야함

class PostWidget extends StatelessWidget {
  final String uid;
  final String thumbnail;
  final String title;
  final String description;
  final PostClickFunction callBack;

  const PostWidget(
      {required this.uid,
      required this.thumbnail,
      required this.title,
      required this.description,
      required this.callBack});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: callBack,
      child: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // tag가 연결되는 이미지의 tag와 같아야함
            // 리스트의 이미지의 태그와 디테일 페이지의 이미지의 태그가 동일해야함
            Hero(tag: uid, child: Image.asset(thumbnail)),
            Padding(
              padding: const EdgeInsets.only(
                  top: 10, bottom: 20, left: 10, right: 10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    title,
                    style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
                  ),
                  Text(
                    description,
                    style: TextStyle(
                      fontSize: 12,
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

아래는 디테일 페이지

class PostDetailView extends GetView<TextAnimation> {
  const PostDetailView();

  @override
  Widget build(BuildContext context) {
    Get.put(TextAnimation());
    return Scaffold(
      appBar: AppBar(
        title: Text(controller.post!['title']!),
      ),
      body: Column(
        children: [
          Hero(
              tag: controller.post!['uid']!,
              child: Image.asset(controller.post!['thumbnail']!)),
          Column(
            children: [
              Text(
                controller.post!['title']!,
              ),
              Text(
                controller.post!['description']!,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
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: 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

+ Recent posts