OSXDev BootCamp에서 코어 애니메이션(Core Animation)에 대한 세션을 봤는데, 꽤나 인상적이었다. 마치 웹에서 Script.aculo.us 전후로 세상이 바뀐 것처럼, 데스크탑에는 코어 애니메이션이 새로운 세상을 열어주는 것 같았다.

기회가 되면 코어 애니메이션을 체험해봐야지라고 생각하고 있었는데, 맥루비(MacRuby)와도 익숙해질겸, 간단한 애플리케이션을 만들어봤다.
Jumpy
Jumpy는 Scott Stevenson씨가 만들어 공개한 3가지 코어 애니메이션 예제중 하나다. 그 중 Jumpy가 가장 간단해보여서 맥루비로 포팅해보기로 했다. 일단 동영상을 보자.
실행하면 현재 화면을 캡쳐해서 뒤로 통통튀며 사라지는 간단한 효과를 구현한 애플리케이션이다. 이 예제에는 3가지 애니메이션이 복합되어 있다.
- 크키가 작아진다
- 점점 흐려진다(Fade Out)
- 통통 튄다.
창을 만들고 레이어를 구성한다
먼저 메인 윈도우를 만든다.
- def main_window
returning(:main_window, NSWindow.alloc.initWithContentRect(NSScreen.mainScreen.frame,
styleMask: ::NSBorderlessWindowMask, backing: ::NSBackingStoreRetained,
defer: false, screen: NSScreen.mainScreen)) do |win|
win.contentView.wantsLayer = true
win.contentView.layer.backgroundColor = CGColorCreateGenericGray(0.0, 1.0)
end
end
returning은 계산 결과를 캐싱하기 위해 만든 함수다. 위에서는 @main_window 인스턴스가 있으면 이 값을 반환하고 없으면 블록을 수행한다. 그래서 NSScreen.mainScreen.frame 사이즈로 검은색 배경의 윈도우를 하나 만들었다.
이 윈도우에 레이어를 추가해보자.
먼저 화면의 스크린샷 이미지를 만든다. CGWindowListCreateImage 메서드를 이용한다.
- def snapshot_image
returning :snapshot_image, CGWindowListCreateImage(
::CGRectInfinite, ::KCGWindowListOptionOnScreenOnly,
::KCGNullWindowID, ::KCGWindowImageDefault)
end
이 스냅샷을 담은 레이어의 이름은 스크린 레이어다.
- def screen_layer
returning :screen_layer, CALayer.layer do |layer|
layer.frame = cgrect(0.0, 0.0, CGImageGetWidth(snapshot_image), CGImageGetHeight(snapshot_image))
layer.contents = snapshot_image
end
end
그리고 아래에 스크린레이어가 비친 형상의 리플렉션 레이어를 만든다. 웹 2.0 사이트에서 인기있는 바로 그 효과다.
- def reflection_layer
returning :reflection_layer, CALayer.layer do |layer|
layer.contents = screen_layer.contents
layer.opacity = 0.4
layer.frame = CGRectOffset(screen_layer.frame, 0.5, -NSScreen.mainScreen.frame.size.height + 0.5)
layer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) # flip the y-axis
layer.sublayerTransform = reflection_layer.transform
end
end
이제 이 두개의 레이어를 포함하는 컨테이너 레이어다.
- def container_layer
returning :container_layer, CALayer.layer do |layer|
container_layer.frame = screen_layer.frame
container_layer.addSublayer(screen_layer)
container_layer.addSublayer(reflection_layer)
end
end
애니메이션 효과
위에서 만든 컨테이너 레이어에 애니메이션 효과를 부여해보자. 먼저 점점 작아지는 애니메이션이다.
- def scale_to(val = 0.0)
shirink = CABasicAnimation.animationWithKeyPath('transform.scale')
shirink.toValue = val
shirink.timingFunction = easy_in_timing_function
container_layer.addAnimation shirink, forKey: 'shirinkAnimation'
end
타이밍 펑션은 EasyIn으로 점점 느려지는 것이다.
- def easy_in_timing_function
CAMediaTimingFunction.functionWithName(::KCAMediaTimingFunctionEaseIn)
end
꽤 간단한 코드로 작어지는 애니메이션을 구현할 수 있다. 코어 애니메이션의 매력이다.
다음에는 페이드 아웃 효과다.
- def fade_to(val = 0.0)
fade = CABasicAnimation.animationWithKeyPath('opacity')
fade.toValue = val
fade.timingFunction = easy_in_timing_function
container_layer.addAnimation fade, forKey: 'fadeAnimation'
end
투명도를 1.0에서 0.0으로 점점 바꿔가는 것이다.
마지막으로 이 애플리케이션의 핵심 통통 튀는 효과다.
- def make_jump(n = 5)
path = CGPathCreateMutable()
CGPathMoveToPoint path, nil, container_layer.position.x, container_layer.position.y
1.upto(n) do |i|
CGPathAddQuadCurveToPoint path, nil, container_layer.position.x, container_layer.position.y*i, container_layer.position.x, container_layer.position.y
end
jump = CAKeyframeAnimation.animationWithKeyPath('position')
jump.path = path
jump.timingFunction = easy_in_timing_function
container_layer.addAnimation jump, forKey: 'JumpAnimation'
end
Path를 만들어 position을 바꿔주기만 하면 된다. Path는 점점 높게 5번 튀도록 되어 있다.
고고!
이제 지금까지 만든 효과와 레이어를 모두 한 곳에서 돌아가게만 하면 된다. applicationDidFinishLaunching 메시지가 적당한 위치일 것이다. 먼저 메인 윈도우를 만들고 거기에 레이어를 추가한다.
- main_window.contentView.layer.addSublayer(container_layer)
main_window.setLevel CGShieldingWindowLevel()
main_window.makeKeyAndOrderFront nil
그리고 CATransaction을 이용해 애니메이션을 수행한다.
- def ca_transaction
CATransaction.begin
yield
CATransaction.commit
end
- ca_transaction do
CATransaction.setValue ANIMATION_DURATION, forKey: ::KCATransactionAnimationDuration
scale_to(0.0)
fade_to(0.0)
make_jump
end
마지막으로 애니메이션이 종료되면 애플리케이션도 함께 종료하는 코드도 잊지 않는다.
- ::NSApp.performSelector('terminate:', withObject:nil, afterDelay: ANIMATION_DURATION)
소스 코드
후기
이 예제를 옮겨보며 맥루비와 코어 애니메이션의 기본적인 구조를 파악할 수 있었다. 무엇보다 코어 애니메이션이 꽤나 재밌다는 사실을 알았다. 다음에는 소스코드가 통통튀어다니면서 화면을 채우는 스크린세이버를 하나 만들어보면 어떨까라고 생각을 해본다.
참고
