3.1 Basic Animation
CABasicAnimation
3.2 Keyframe Animations
与基本动画进行线性插值不同,关键桢动画允许你在特定时间指定不同的数值,这样你可以在动画的全过程控制动画的展示和动作。
自从发明手绘电影以来,关键桢就在动画中起到了一定作用。一些资深的艺术家会针对一个场景画不同的关键桢,而一些刚入行的艺术家却会填满每一桢,以便使动画看起来更平滑。(有时候这个过程叫做tweening:两者之间的动画)。Core Animation的关键桢动画可以帮我们实现在关键桢之间的填充,我们只需要指定哪个是关键桢,系统会自动帮我们生成动画。
另外一个很酷的东西是,关键桢动画同样可以完成任何基本动画能够完成的动作。特别是点、大小或是矩形。比如如果我们希望让一个层的不透明度显示为一个动画,我们可以指定一些不同的数值(比如:0.25, 0.50, 0.75),然后在动画的过程的不同时间中逐渐变化。
再一个真的很酷的方面,我们不光可以使用离散数值,更可以使用CGPath去指定关键桢动画的值。换句话说,我们不仅仅可以在特定时间点指定特定的动画值,还可以使用曲线路径去指定动画的值。举例说明,我们可以将一个层的不透明度用钟型曲线表示:开始是透明,然后逐渐显现到某个特定透明度,再逐渐变为透明。我们还可以用CGPoint和CGSize值的路径做为动画的行进路线。这样,路径的x值可以表示横坐标也可以表示宽度,y既可以表示纵坐标还可以表示高度。
The code to create the keyframe animation would look like this:
- (CAKeyframeAnimation *)opacityAnimation { CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.values = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.75], [NSNumber numberWithFloat:0.0], nil]; animation.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.25], [NSNumber numberWithFloat:0.50], [NSNumber numberWithFloat:0.75], nil]; return animation; }
不设置keyTimes时的运行方式:
Keyframes and Paths
- (void)addBounceAnimation { [mover setAnimations:[NSDictionary dictionaryWithObjectsAndKeys: self.originAnimation, @"frameOrigin" , nil]]; } - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { CGFloat xInset = 3.0f * (NSWidth(frame) / 8.0f); CGFloat yInset = 3.0f * (NSHeight(frame) / 8.0f); NSRect moverFrame = NSInsetRect(frame, xInset, yInset); mover = [[NSImageView alloc] initWithFrame:moverFrame]; [mover setImageScaling:NSScaleToFit]; [mover setImage:[NSImage imageNamed:@"photo.jpg" ]]; [self addSubview:mover]; [self addBounceAnimation]; } return self; }
[mover setAnimations:[NSDictionary dictionaryWithObjectsAndKeys: self.originAnimation, @" frameOrigin " , nil]];
frameOrigin:
要和[[mover animator] setFrameOrigin:rect.origin];的
setFrameOrigin相对应,才会在setFrameOrigin方法被调用的时候,执行设置的动画
例如:
- (void)awakeFromNib
{
NSView *contentView = [[self window] contentView];
[contentView setWantsLayer:YES];
[contentView addSubview:[self currentView]];
transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:kCATransitionFromLeft];
NSDictionary *ani = [NSDictionary dictionaryWithObject:transition
forKey:@"subviews"];
[contentView setAnimations:ani];
}
awakeFromNib首先打开Core Animation支持(就是setWantsLayer这行)。然后把当前的参考视图currentView做为子视图加入到contentView中。由于我们已经将currentView的frameOrigin属性设置为0,0,因此不需要考虑subview的位置。
接下来我建立了一个CATransition的动画。注意我在AppDelegate中将这个动画保留为ivar。原因很明显,当动画建立时,我将它做为transition动画加入content view,key是“subviews”。这个transition动画无论在一个subview添加、删除或者替换的时候都会触发。
- (CAKeyframeAnimation *)originAnimation { CAKeyframeAnimation *originAnimation = [CAKeyframeAnimation animation]; originAnimation.path = self.heartPath; originAnimation.duration = 2.0f; originAnimation.calculationMode = kCAAnimationPaced; return originAnimation; }
设置动画运行路径:
originAnimation.path = self.heartPath;
设置动画持续时间:
originAnimation.duration = 2.0f;
设置动画方式:
originAnimation.calculationMode = kCAAnimationPaced;
设置动画路径:
- (CGPathRef)heartPath { NSRect frame = [mover frame]; if(heartPath == NULL) { heartPath = CGPathCreateMutable(); CGPathMoveToPoint(heartPath, NULL, NSMinX(frame), NSMinY(frame)); CGPathAddLineToPoint(heartPath, NULL, NSMinX(frame) - NSWidth(frame),NSMinY(frame) + NSHeight(frame) * 0.85); CGPathAddLineToPoint(heartPath, NULL, NSMinX(frame),NSMinY(frame) - NSHeight(frame) * 1.5); CGPathAddLineToPoint(heartPath, NULL, NSMinX(frame) + NSWidth(frame),NSMinY(frame) + NSHeight(frame) * 0.85); CGPathAddLineToPoint(heartPath, NULL, NSMinX(frame), NSMinY(frame)); CGPathCloseSubpath(heartPath); } return heartPath; }
- (void)bounce { NSRect rect = [mover frame]; [[mover animator] setFrameOrigin:rect.origin]; }
Recall that since we have added an animation to the animations dictionary under the frameOrigin key, the animator will find it during its search and use ours instead of the default animation.