seong

Flutter - Pro Animate 3D 입체감 살리기 본문

Flutter/Flutter

Flutter - Pro Animate 3D 입체감 살리기

hyeonseong 2023. 9. 14. 14:29

마지막으로 내가 이 클론코딩을 선택한 이유중 한개인 SideMenu클릭시 3D 입체감 표현이다.

\

시작 전 기본적으로 화면은 Stack으로 구성이 되어 있고,

AnimatedPosition, Transform이 주로 사용된다.

 

1. SideMenu 먼저 호출

	Positioned(
            width: 288,
            height: MediaQuery.of(context).size.height,
            child: const SideMenu(),
          ),

2. HomeScreen 에  translate 적용

Transform.translate는 자식 위젯을 이동시켜주는 역할을 한다.

Transform.translate(
	offset: Offset(isSideMenuClosed ? 0 : 288,0), // x : 288, y : 0
    child: HomeScreen()
)

3. HomeScreen 사이즈 조정 

scale는 자식 위젯의 사이즈를 조정해준다.

Transform.translate(
	offset: Offset(isSideMenuClosed ? 0 : 288,0),
    child: Transform.scale(
        scale:isSideMenuClosed ? 1 : 0.8,
        child:const ClipRRect(
                    borderRadius: BorderRadius.all(
                      Radius.circular(24),
                    ),
                    child: HomeScreen()),
    ),

),

4. SideMenu에 Animate추가

duration : 0.2초동안 Animate실행

curve : Animate의 속도를 제어

left: isSideMenuClosed ? -288 : 0  //  isSideMenuClosed = true라면 SideMenu를 화면상 -288로 이동시킨다.

288과 -288은 언뜻 보기엔 다른점이 별로 없었다, 하지만 자세히보면 SideMenu가 생겼다가 다시 들어갈때 -288은 왼쪽으로 사라지고 288은 오른쪽으로 사라진다.
쉽게 말해, SideMenu가 288일 경우 HomeScreen 아래로 사라지는것 처럼 보이게된다.
 AnimatedPositioned(
            duration: const Duration(milliseconds: 200),
            //curve는 애니메이션의 속도를 제어한다, fastOutSlowIn는 시작할 때 빠르게 가속하고, 중간에 천천히 감속
            curve: Curves.fastOutSlowIn,
            width: 288,
            //isSideMenuClosed가 true이면 그대로, 만약 클릭되었다면 -288.
            left: isSideMenuClosed ? -288 : 0,
            height: MediaQuery.of(context).size.height,
            child: const SideMenu(),
          ),

 

4번까지 SideMenu클릭 시 HomeScreen의 위치 조정 및 크기는 어느정도 정해졌다.

이제 HomeScreen이 이동할때 부드럽게이동 하는것과, 3D Animate를 입혀주면 된다.

5. SingleTickerProviderStateMixin 추가, AnimationController, Animation 선언 및 초기화

SingleTickerProviderStateMixin을 상속해 vsync를 매개변수로 받아서 AnimationController를 만들 수 있다.

만약 다중 AnimationController를 사용할 시 TickerProviderStateMixin 를 사용해주는게 좋다.

SingleTickerProviderStateMixin은 애니메이션 컨트롤러를 제어하는 데 사용되고, TickerProvider 구현부 중 일부이다.
TickerProvider는 애니메이션 프레임을 제공해준다.

controller 초기화 

vsync는 필수 속성이며, 애니메이션을 언제 재생할지와, 위젯의 생명주기와 동기화 시켜준다.

animation을 초기화 하고, 리스너를 등록해주게 되면, animation이 동작하고 난 후 리스너에 등록된 setState가 동작해 rebuild가 된다.

  @override
  void initState() {
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
    )..addListener(() {
        setState(() {});
      });
 
    animation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.fastOutSlowIn),
    );
    scaleAnimation = Tween<double>(begin: 1, end: 0.8).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.fastOutSlowIn),
    );
    super.initState();
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

6. Transform 에 animation값 적용

Transform.translate(
			//가로로 265만큼 이동
              offset: Offset(animation.value * 265, 0),
              //scale는 자식 위젯의 크기를 조정한다. 주로 동적으로 변하는 화면에 사용할 수 있다.
              child: Transform.scale(
              //1 -> 0.8만큼
                scale: scaleAnimation.value,
                child: const ClipRRect(
                    borderRadius: BorderRadius.all(
                      Radius.circular(24),
                    ),
                    child: HomeScreen()),
              ),
            ),

7. Animate 시작

SideMenu버튼 클릭시 화면이 움직이기 때문에 버튼의 onPress부분에 .forward()메서드를 넣어주었다.

.foward() : animate 시작

.reverse() : animate 반대로

AnimatedPositioned(
            // SideMenu가 열려있을 경우 x버튼을 오른쪽으로 이동
            duration: const Duration(milliseconds: 200),
            curve: Curves.fastOutSlowIn,
            left: isSideMenuClosed ? 0 : 220,
            top: 16,
            child: MenuBtn(
              riveOnInit: (artboard) {
                StateMachineController controller = RiveUtils.getRiveController(artboard, stateMachineName: "State Machine");
                isSideBarClosed = controller.findSMI("isOpen") as SMIBool;
                isSideBarClosed.value = true;
              },
              press: () {
                isSideBarClosed.value = !isSideBarClosed.value;
                
                //SideBar가 닫혀 있으면(true) animate 시작
                if (isSideMenuClosed) {
                  _animationController.forward();
                } else {
                  //animate reverse(역방향)
                  _animationController.reverse();
                }
                setState(() {
                  isSideMenuClosed = isSideBarClosed.value;
                });
              },
            ),
          ),

8. 이제 마지막 단계인 3D를 적용 시켜준다.

setEntry() : 원근감,

rotateY() : Y축(세로)을 기준으로 회전 , 만약 X축(가로)을 원한다면 rotateX()를 사용하면 된다.

이것에 대한 자세한 설명은 https://lucky516.tistory.com/123 이 글이 설명이 아주 잘 되어있다.
Transform(
            //3D화면
            alignment: Alignment.center,
            transform: Matrix4.identity()
            //4 x 4 에서 x축으로 3, y축으로 2 에 0.001을 설정.
              ..setEntry(3, 2, 0.001)
              // 1-(30 * 1 * pi / 180)
              ..rotateY(animation.value - 30 * animation.value * pi / 180),
            child: Transform.translate(
              offset: Offset(animation.value * 265, 0),
              //scale는 자식 위젯의 크기를 조정한다. 주로 동적으로 변하는 화면에 사용할 수 있다.
              child: Transform.scale(
                scale: scaleAnimation.value,
                child: const ClipRRect(
                    borderRadius: BorderRadius.all(
                      Radius.circular(24),
                    ),
                    child: HomeScreen()),
              ),
            ),
          ),