2017년 2월 21일 화요일

CALayer Animation (Core Animation)

CALayer 에 관한 글을 적으면서 애니메이션에 대해 언급할 필요가 생겼는데 간략히 하기에는 양이 너무 많은 것 같다. 그래서 이번엔 CALayer 애니메이션에 관한 것을 별도로 정리하려고 한다.

CALayer 의 이름에서 볼 수 있는 머릿글자 CA 는 Core Animation 의 약자이다. Quartzcore 라는 거대한 시스템의 일부이기도 한 이 Core Animation 은 레이어 애니메이션을 위한 기능들로 구성되어 있다.

전체를 설명하기에는 양의 방대함도 그렇고 개인적인 지식도 문제가 되기 때문에 애니메이션과 관련된 Quartzcore 의 몇 클래스만을 정리해 본다.

아래 영상은 이 글의 보조자료(?)로써 아무래도 눈으로 움직이는 것을 보는게 좋을 것 같아 별도로 만들어 봤으니 참고하자.

CABasicAnimation

이름 답게 기초적인 애니메이션을 제공하는 클래스이다. keyPath 로 지정된 프로퍼티의 값을 fromValue 에서 부터 toValue 까지 점진적으로 (혹은 timingFunction 알고리즘에 맞게) duration 시간 만큼 변화시킨다.
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(...)
animation.toValue = CGPoint(...)
animation.duration = ...
layer.add(animation, forKey: "simple position animation")
위의 예는 position 프로퍼티를 변경시키는 애니메이션이다. 애니메이션 생성자에서 keyPath 값으로 넘기는 문자열은 KVC(Key-Value Coding) 이라는 방식으로 표현한 프로퍼티이다. 이에 관한건 글 하단의 관련글 링크를 참고하자.

공통적으로 모든 애니메이션은 CALayer 의 add(_:forKey:) 라는 메소드를 이용해 레이어에 애니메이션을 부여 할 수 있다. 여기서 쓰이는 key 라는 값은 애니메이션의 고유 이름이라고 생각하면 된다. 즉 아무 문자나 넣어도 관계없다. 나중에 애니메이션을 관리해야 할 때 별도로 필요하겠지만 단순한 애니메이션의 경우 별로 신경 쓸 필요는 없다.

위의 예는 딱딱한 애니메이션이 되는데, 타이밍 함수를 이용하면 정적인 애니메이션이 아니라 좀 더 부드러운 애니메이션을 만들 수도 있다. 예를 들어 미리 정의된 ease in ease out 같은 알고리즘을 쓰려면 아래와 같이 타이밍 함수를 지정해 주면 된다.
animation.timingFunction = 
  CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

CAKeyframeAnimation

아마도 가장 자주 사용 할 만한 애니메이션 클래스이다. 키 프레임이란 애니메이션의 중간 상태를 의미한다고 볼 수 있는데, 이런 프레임 정보 여럿을 이용해 애니메이션을 구성할 수 있다.

key path 에 따라 몇 가지 사용해야 할 프로퍼티들이 따로 있다. 예를 들어 만화영화 같은 애니메이션, 즉 여러 장의 그림을 빠르게 바꾸어서 애니메이션을 만드는 경우라면 아래와 같이 쓸 수 있다.
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.values = [ image1, image2, ... ]
animation.repeatCount = .greatestFiniteMagnitude
animation.duration = ...
layer.add(animation, forKey: "frame animation")
위의 경우 CALayer 의 contents 즉 이미지를 바꾸는 애니메이션을 정의한 코드이다. values 의 값은 결국 contents 에 들어가야 할 값으로, 여기서는 CGImage 인스턴스 배열로 선언되었다고 가정한다.

repeatCount 는 이름대로 반복 횟수인데, 여기서 사용한 .greatestFiniteMagnitude 라는 값은 영원히 반복하라는 의미이다.

물론 이 외에도 경로(path)를 이용한 애니메이션도 가능하다. 이 경우 values 가 아닌 path 프로퍼티를 이용한다.
let path = CGPath()
...

let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path
animation.duration = ...
layer.add(animation, forKey: "path animation")
path 에 대해 잘 모른다면 CAShapeLayer 의 내용에서 간략히 설명하고 있는 부분을 참고하자. 하여간 위 코드는 path 에서 정의한 곡선 모양으로 레이어를 움직이는 애니메이션을 만들어낸다.

CASpringAnimation

이름 대로 스프링 같은 탄성이 있는 애니메이션을 만들어내는 특수한 클래스이다. 사용법은 CABasicAnimation 과 비슷하지만 damping 같은 특수한 탄성 프로퍼티 등도 추가로 제공된다.
let animation = CASpringAnimation(keyPath: "position")
animation.fromValue = CGPoint(...)
animation.toValue = CGPoint(...)
animation.damping = ...
layer.add(animation, forKey: "spring animation")
damping(탄성) 의 값을 크게 주면 덜 흔들리고, 반대로 작게 주면 심하게 요동치는 모습을 볼 수 있다. 참고로 기본값은 10이다.

CAAnimationGroup

여러 애니메이션을 하나의 애니메이션 처럼 만들기 위해 그룹을 지어 줄 수 있다. CAAnimationGroup 은 이런 그룹을 만들기 위한 클래스이다.
let group = CAAnimationGroup()
group.animations = [animation1, animation2, ...]
group.duration = ...
layer.add(group, forKey: "group animation")
이런 식으로 그룹 자신이 애니메이션인 것 처럼(?) 사용 할 수 있다. 여기서 duration 은 animation1 과 animation2 가 이 시간 만큼은 움직인다는 의미이다. 물론 각 애니메이션에 지정된 duration 값들이 우선시 되겠지만...

그룹의 경우는 평행, 즉 동시에 움직이는 애니메이션을 정의하기 위한 요소라서 순차적인 애니메이션(Sequenced Animation)을 직접 만들 순 없다. 하지만 애니메이션의 beginTime 을 응용하면 순차적인 애니메이션을 만들 수도 있다.
animation1.duration = 2

animation2.duration = 3
animation2.beginTime = 2

let group = CAAnimationGroup()
group.animations = [animation1, animation2]
group.duration = 5
layer.add(group, forKey: "sequenced animation")
참고로 beginTime 값은 애니메이션 시작 딜레이를 주기 위한 값인데, 그룹이 아닌 독자적으로 쓰이는 경우에는 절대시각 값이 필요하다. 예를 들어 한 단독 애니메이션이 5초 후에 애니메이션이 동작하길 원한다면
animation.beginTime = CACurrentMediaTime() + 5
이런 식으로 현재 시간을 구해서 거기에 필요한 딜레이 시간을 더해야 한다.

CATransaction

CATransaction 은 하나의 애니메이션 단위를 기준으로 뭔가를 하기 위한 녀석인데 설명하기에는 좀 난감하다. 단지, 여기서는 애니메이션이 완료되었을 때 뭔가를 하고 싶은 경우를 위해서 언급한다. (영어로 표현하자면 completion handler 혹은 completion block 말이다)
CATransaction.begin()

let animation = CABasicAnimation(keyPath: ...)
...
CATransaction.setCompletionBlock {
  print("Animation Finished")
}

layer.add(animation, forKey: "completion handler test")

CATransaction.commit()
위의 코드는 CATransaction 을 이용해 애니메이션이 완료되었을 때 로그를 찍도록 하기 위한 예이다.

물론 이 외에도 애니메이션의 delegate 를 구현해서 활용하는 방법도 이용할 수 있겠지만 개인적으론 클로져(블럭)를 선호하기에 생략한다. :-)

[관련글] CALayer 시작하기
[관련글] CALayer - CAShapeLayer
[관련글] Animatable Properties (Core Animation Programming Guide)
[관련글] Key-Value Coding Extensions (Core Animation Programming Guide)

댓글 3개 :

jusung :

헐~~~ 영상까지!! 잘봤습니다.
이제 유투버 하시는 건가요? ^^

Renn Seo :

에 좀 낡아서 허물어져가는 개인 채널이 있긴 있었습니다. ;-)

jusung :

ㅋㅋㅋ ;)