50mm, 1/20s, resized
https://discussions.apple.com/thread/6567790?tstart=0
CAKeyframeAnimation 사용하기
키프레임 애니메이션은 특정한 시간(키프레임)에 객체가 가져야 할 값을 지정하고, 그 사이의 값을 보간(interpolation)하는 방법으로 구현하는 애니메이션입니다.
예를 들어 공이 떨어져서 튀어오르는 애니메이션을 구현하기 위해서, 공의 초기값, 바닥값, 튀어올랐을 때의 최대값, 다시 바닥에 떨어졌을 때의 값에 대해 각각 시간과 위치를 입력하고, 나머지의 값은 자동으로 계산되도록 하는 것입니다.
키프레임 애니메이션을 구현하기 위해 CAKeyframeAnimation이 제공됩니다. 다음은 간단한 예제입니다.
- (void)keyframeAnimation
{
CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
ani.duration = 0.5;
NSArray* values = @[ [NSNumber numberWithFloat:160.0],
[NSNumber numberWithFloat:400.0],
[NSNumber numberWithFloat:320.0],
[NSNumber numberWithFloat:400.0]
];
NSArray* keyTimes = @[ [NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.7],
[NSNumber numberWithFloat:0.85],
[NSNumber numberWithFloat:1.0]
];
ani.values = values;
ani.keyTimes = keyTimes;
[self.itemView.layer addAnimation:ani forKey:@"ani" didStart:nil didStop:^(BOOL finished) {
self.itemView.layer.position = CGPointMake(self.itemView.layer.position.x, 400.0);
}];
}
keyTimes 배열과 values 배열은 1:1 대응되며, keyTimes의 시간축은 항상 0.0 ~ 1.0입니다.
위 예제는 간단한 바운스를 구현한 예제입니다. 보간법을 지정하지 않았을 경우에는 각 키프레임 사이의 값이 linear하기 때문에 위의 예제는 실행시키면 상당히 어색한 움직임을 보입니다.
보간법을 지정하기 위해서 몇가지 방법이 있는데, 그중에서 보편적인 keyTimes를 이용해 보도록 하겠습니다.
앞서 CABasicAnimation에서 timingFunction 프로퍼티에 CAMediaTimingFunction을 지정하여 애니메이션의 흐름을 조정하는 방법을 사용했습니다.
키프레임 애니메이션에서는 각 키프레임마다 이를 설정할 수 있도록 timingFunctions라는 프로퍼티를 제공합니다.
키프레임이 4개일 경우, 각 키프레임 사이를 잇는 역할을 하는 timingFunction은 3개가 필요합니다. (n-1)
위의 예제에 timingFunctions를 추가하여 조금 자연스러운 애니메이션을 만들어 보도록 하겠습니다.
- (void)keyframeAnimation
{
CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
ani.duration = 0.5;
NSArray* values = @[ [NSNumber numberWithFloat:160.0],
[NSNumber numberWithFloat:400.0],
[NSNumber numberWithFloat:320.0],
[NSNumber numberWithFloat:400.0]
];
NSArray* keyTimes = @[ [NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.7],
[NSNumber numberWithFloat:0.85],
[NSNumber numberWithFloat:1.0]
];
NSArray* timingFunctions = @[
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]
];
ani.values = values;
ani.keyTimes = keyTimes;
ani.timingFunctions = timingFunctions;
[self.itemView.layer addAnimation:ani forKey:@"ani" didStart:nil didStop:^(BOOL finished) {
self.itemView.layer.position = CGPointMake(self.itemView.layer.position.x, 400.0);
}];
}
애니메이션에 기본 제공되는 timing function을 사용하여 조금 더 부드러운 애니메이션을 만들었습니다.
아까보다는 낫지만, 기본 함수는 곡선이 다소 완만하기 때문에 중력을 표현하기엔 아직 다소 부자연스럽습니다.
베지어 곡선을 직접 만들어서 지정해 보도록 하겠습니다.
- (void)keyframeAnimation2
{
CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
ani.duration = 0.5;
NSArray* values = @[ [NSNumber numberWithFloat:160.0],
[NSNumber numberWithFloat:400.0],
[NSNumber numberWithFloat:320.0],
[NSNumber numberWithFloat:400.0]
];
NSArray* keyTimes = @[ [NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.7],
[NSNumber numberWithFloat:0.85],
[NSNumber numberWithFloat:1.0]
];
NSArray* timingFunctions = @[
[CAMediaTimingFunction functionWithControlPoints:0.5 :0 :1 :0.5],
[CAMediaTimingFunction functionWithControlPoints:0 :0.5 :0.5 :1],
[CAMediaTimingFunction functionWithControlPoints:0.5 :0 :1 :0.5]
];
ani.values = values;
ani.keyTimes = keyTimes;
ani.timingFunctions = timingFunctions;
[self.itemView.layer addAnimation:ani forKey:@"ani" didStart:nil didStop:^(BOOL finished) {
self.itemView.layer.position = CGPointMake(self.itemView.layer.position.x, 400.0);
}];
}
Control point에 할당된 값의 곡선은 http://cubic-bezier.com에서 확인할 수 있습니다.
떨어지는 동안은 점점 빨라지도록, 올라가는 동안은 점점 느려지게 보이도록 곡선을 조정한 것입니다.
만약 좀더 물리법칙에 가까운 애니메이션을 만들고자 한다면 계산이 복잡해집니다.
티몬앱에는 TMAnimation이라는 클래스가 있습니다. CAKeyframeAnimation에 꼼수를 써서 다양한 커브를 구현한 내용입니다.
TMAnimation의 커브 계산식은 오픈 소스 게임 프레임워크인 Sparrow에서 가져온 것이므로, 이 문서를 참고하시면 됩니다.
http://doc.sparrow-framework.org/v2/Classes/SPTransitions.html
바운스 예제를 좀더 현실적으로 만들어 보겠습니다. 이번에는 객체가 여러번 튑니다.
- (void)tmonBounceAnimation
{
TMAnimation* ani = [TMAnimation animationWithKeyPath:@"position.y" startValue:50.0 endValue:500.0 duration:2.0 evaluationBlock:TMAnimationEaseOutBounceBlock];
[self.itemView.layer addAnimation:ani forKey:@"ani"];
}
음.. 코드가 너무 간단해져 버렸네요.;;
사실 TMAnimation의 원리는 100개의 프레임을 갖는 CAKeyframeAnimation입니다.
따라서… 너무 자주 쓰시면 안됩니다.
그리고 startValue와 endValue가 float밖에 안되는 것은… 구현자의 게으름 때문입니다.
이부분은 필요할 때 NSValue등을 넣을 수 있도록 수정해서 쓰시면 되겠습니다.
Evernote Data:
{"source": "missing value", "created": "2014년 5월 28일 수요일 오후 5:16:30", "modified": "2014년 5월 28일 수요일 오후 6:03:59", "tags": "", "altitude": "48.0", "latitude": "37.51442024", "longitude": "127.1016053" }