렌더링을 역동적이게!<1>

enoch·2022년 1월 11일
1

플러터

목록 보기
13/17
post-thumbnail

1. 개요

렌더링과정은 항상 고민이 되는 부분이다. 그냥 띡하고나오기엔 너무 못생겼고, 스켈레톤가져다박는다해도, 위젯이 고정된 크기라면 모를까, 커졌다작아졌다하는 위젯이라면 오히려 스켈레톤 가져다 박는게 못생겼다.

그래서 이것저것 복합적으로 사용하면서 렌더링을 조금더 역동적이게 해보려한다.
참고로 이번껀 내가 못해서인지 뭔지 모르겠지만 조금 복잡하다.

2. 구현

일단 만드려고하는 느낌은 대강 이렇다.

먼저 구상을해보자
제일먼저 백엔드와 통신을 하는 느낌을 주기위해서 딜레이를 줘봤다

class _Jan4State extends State<Jan4> {
  bool _render = false;
  @override
  void initState() {
    super.initState();
    Future.delayed(const Duration(milliseconds: 1000), () {
      if (mounted) {
        setState(() {
          _render = true;
        });
      }
    });
  }

여기서 살짝에 설명을붙이자면 저 mounted없이 그냥 구현한뒤 해당 setState가 실행하기도전에 위젯을 dispose하면 에러가뜬다.(setstate after dispose 어쩌구 저쩌구) 그렇기때문에 필수로 붙이자.
프로젝트 진행하면서 저걸 처음에 몰랐어서 에러날때마다 골아팠었다.

그리고 해당 위젯의 틀이 먼저 렌더링되고 그안에 위젯이 스르륵 나타나는 효과를 주기위해서 위젯을 둘로 나누었다.

먼저 틀위젯을 만들어보자

(context, child) => Container(
                          width: double.infinity,
                          decoration: BoxDecoration(
                              borderRadius: BorderRadius.circular(10),
                              color: Colors.grey[200],
                              boxShadow: const [
                                BoxShadow(),
                              ]),
                          child: child,
                        ),

그리고 안에 집어넣을 위젯을 만들어보자

(context) => SizedBox(
                          height: 100,
                          child: Center(
                            child: Text(
                              "짠",
                              style: Theme.of(context).textTheme.bodyText1,
                            ),
                          ),
                        ),

근데 여기서 왜 그냥 위젯이아니라 위젯빌더 즉 위젯을 리턴하는 함수를 만들었냐면. 백엔드 통신이후에 해당위젯에서 값을보여줘야하는데 위젯을 바로넣어버리면 null이라고 난리난리친다. 무튼 이렇게 빌더로 집어넣어주면 그런 상황이 확연이 줄고 null체크 도배를 조금 막을수는 있었지만. 더욱더 좋은 방법이 있을수도?

일단 필자는 그렇게 했다.

대강 위젯을 만들고 이젠 스르륵 나타나는 애니메이션을 만들어보자

class _WidgetAppearState extends State<WidgetAppear>
    with TickerProviderStateMixin {
  late final AnimationController _animationController = AnimationController(
      vsync: this, duration: const Duration(milliseconds: 300));
  late final Animation _animation = Tween<double>(begin: 0, end: 1)
      .chain(CurveTween(curve: Curves.easeInOut))
      .animate(_animationController);

  late final AnimationController _afterAnimationController =
      AnimationController(
          vsync: this, duration: const Duration(milliseconds: 200));
  late final Animation _afterAnimation = Tween<double>(begin: 0, end: 1)
      .chain(CurveTween(curve: Curves.easeInOut))
      .animate(_afterAnimationController);

잘보면 알겠지만 일단 애니메이션이 두개이다. 먼저 위젯이 커지게하는 애니메이션이 재생된후에 안의 위젯의 opacity를 변경해주는 애니메이션 두개가 필요하다.

그래서 두개의 Ticker를 쓰기때문에 with뒤 singleTicker를 사용하면 안된다!

 Widget build(BuildContext context) {
    if (widget.stop && widget.condition == true) {
      return widget.frameBuilder(context, widget.builder(context));
    }
    if (widget.condition) {
      _animationController
          .forward()
          .then((_) => _afterAnimationController.forward());
      return AnimatedBuilder(
          animation: _animation,
          builder: (context, _) {
            return Container(
              constraints: BoxConstraints(
                  maxHeight: _animation.value * widget.maxHeight),
              child: widget.frameBuilder(
                  context,
                  AnimatedBuilder(
                      animation: _afterAnimation,
                      builder: (context, _) => Opacity(
                          opacity: _afterAnimation.value,
                          child: widget.builder(context)))),
            );
          });
    }
    return Container();
  }

빌드부분이다. AnimatedBuilder를 사용하면 animation의 값이 변경될때마다 하위 빌더의 위젯이 변경된다. BoxConstraints를 써서 maxheight만주면 column안에서도 컨테이너의 크기가 변경이 가능하다. 하지만 maxheight을 너무 크게줘버리면 애니메이션이 도중 짤린것처럼 보이기때문에 적당량 주는것이 중요!

그리고 animation이 매번 재생되는것을 방지하기위해 stop정도의 옵션을 주는것도 나쁘지않은것같다.

무튼 오늘은 요기까지.
다음엔 스켈레톤까지 결합해서 한번 만들어보려한다
고럼이만~

profile
플러터존잼

0개의 댓글