blog comments 0 del.icio.us bookmarks 0 diggs 0 Google results 0

1.0
PostRank

JumpR - 맥루비와 코어애니메이션의 만남

From myRuby.net, 5 months ago, 0 views

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

 

coreanimation.thumbnail.jpg

기회가 되면 코어 애니메이션을 체험해봐야지라고 생각하고 있었는데, 맥루비(MacRuby)와도 익숙해질겸, 간단한 애플리케이션을 만들어봤다.

 

Jumpy

Jumpy는 Scott Stevenson씨가 만들어 공개한 3가지 코어 애니메이션 예제중 하나다. 그 중 Jumpy가 가장 간단해보여서 맥루비로 포팅해보기로 했다. 일단 동영상을 보자.

 

 

실행하면 현재 화면을 캡쳐해서 뒤로 통통튀며 사라지는 간단한 효과를 구현한 애플리케이션이다. 이 예제에는 3가지 애니메이션이 복합되어 있다.

 

 

창을 만들고 레이어를 구성한다

먼저 메인 윈도우를 만든다.

 

  1.   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 메서드를 이용한다.

 

  1.   def snapshot_image
        returning :snapshot_image, CGWindowListCreateImage(
          ::CGRectInfinite, ::KCGWindowListOptionOnScreenOnly,
          ::KCGNullWindowID, ::KCGWindowImageDefault)
      end

 

이 스냅샷을 담은 레이어의 이름은 스크린 레이어다.

 

  1.   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 사이트에서 인기있는 바로 그 효과다.

 

  1.   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

 

이제 이 두개의 레이어를 포함하는 컨테이너 레이어다.

 

  1.   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

 

애니메이션 효과

위에서 만든 컨테이너 레이어에 애니메이션 효과를 부여해보자. 먼저 점점 작아지는 애니메이션이다.

 

  1.   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으로 점점 느려지는 것이다.

 

  1.   def easy_in_timing_function
        CAMediaTimingFunction.functionWithName(::KCAMediaTimingFunctionEaseIn)
      end

 

꽤 간단한 코드로 작어지는 애니메이션을 구현할 수 있다. 코어 애니메이션의 매력이다.

 

다음에는 페이드 아웃 효과다.

 

  1.   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으로 점점 바꿔가는 것이다.

 

마지막으로 이 애플리케이션의 핵심 통통 튀는 효과다.

 

  1.   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 메시지가 적당한 위치일 것이다. 먼저 메인 윈도우를 만들고 거기에 레이어를 추가한다.

 

  1.     main_window.contentView.layer.addSublayer(container_layer)
        main_window.setLevel CGShieldingWindowLevel()
        main_window.makeKeyAndOrderFront nil

 

그리고 CATransaction을 이용해 애니메이션을 수행한다.

 

  1.   def ca_transaction
        CATransaction.begin
        yield
        CATransaction.commit
      end

 

  1.     ca_transaction do
          CATransaction.setValue ANIMATION_DURATION, forKey: ::KCATransactionAnimationDuration
          scale_to(0.0)
          fade_to(0.0)
          make_jump
        end

 

마지막으로 애니메이션이 종료되면 애플리케이션도 함께 종료하는 코드도 잊지 않는다.

 

  1.     ::NSApp.performSelector('terminate:', withObject:nil, afterDelay: ANIMATION_DURATION)

 

소스 코드

후기

이 예제를 옮겨보며 맥루비와 코어 애니메이션의 기본적인 구조를 파악할 수 있었다. 무엇보다 코어 애니메이션이 꽤나 재밌다는 사실을 알았다. 다음에는 소스코드가 통통튀어다니면서 화면을 채우는 스크린세이버를 하나 만들어보면 어떨까라고 생각을 해본다.

 

참고

 

 

comments

No comments yet.

You must be logged in to add your own comment.