如何为NSView动画设置曲线效果?NSAnimationContext与NSAnimation方案
嘿,我之前也跟你一样被NSAnimation的文档绕晕过,这两个问题其实都有清晰的解决办法,给你一步步讲清楚:
其实不用绕到NSAnimation类,NSAnimationContext本身就支持直接设置动画曲线,最常用的方式是通过timingFunction属性,你可以用CAMediaTimingFunction来指定各种缓动曲线,包括对应NSAnimationCurve的选项:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) { // 设置动画时长 context.duration = 0.5; // 方式1:用系统预设的媒体计时函数(推荐) context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; // 方式2:直接对应NSAnimationCurve的曲线 context.timingFunction = [CAMediaTimingFunction functionWithControlPointsForAnimationCurve:NSAnimationCurveEaseIn]; // 这里写你的动画逻辑,比如修改view的属性 myView.frame = NSMakeRect(200, 200, 300, 300); myView.alphaValue = 0.5; } completionHandler:^{ // 动画完成后的回调 NSLog(@"动画结束!"); }];
functionWithControlPointsForAnimationCurve:方法可以直接把NSAnimationCurve枚举(比如NSAnimationCurveLinear、NSAnimationCurveEaseInOut等)转换成对应的计时函数,完美适配你熟悉的曲线类型。
NSAnimation是抽象类,不能直接实例化,你有两种选择:用系统提供的子类NSViewAnimation,或者自己子类化NSAnimation实现自定义逻辑。
方式1:用NSViewAnimation(快速实现view属性动画)
这是最简单的方式,直接指定要动画的view和属性(比如frame、alpha等):
// 定义单个view的动画参数 NSDictionary *viewAnimParams = @{ NSViewAnimationTargetKey: myView, NSViewAnimationStartFrameKey: [NSValue valueWithRect:myView.frame], // 起始状态 NSViewAnimationEndFrameKey: [NSValue valueWithRect:NSMakeRect(100, 100, 200, 200)], // 结束状态 NSViewAnimationEffectKey: NSViewAnimationFadeInEffect // 还可以加过渡效果 }; // 创建动画对象 NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:@[viewAnimParams]]; // 设置动画曲线和时长 animation.animationCurve = NSAnimationCurveEaseOut; animation.duration = 0.6; // 开始动画,还可以设置代理监听动画状态 animation.delegate = self; [animation startAnimation];
如果要同时动画多个view,只要在数组里添加多个viewAnimParams字典就行。
方式2:子类化NSAnimation(自定义复杂动画)
如果需要更灵活的动画逻辑(比如非view属性的动画,或者自定义进度更新),可以子类化NSAnimation,核心是利用currentProgress方法——它会自动根据你设置的animationCurve返回缓动后的进度(0到1),不用自己计算曲线:
// 自定义动画类 @interface CustomPropertyAnimation : NSAnimation @property (weak) id target; @property SEL setter; @property CGFloat startValue; @property CGFloat endValue; @end @implementation CustomPropertyAnimation - (instancetype)initWithTarget:(id)target setter:(SEL)setter startValue:(CGFloat)start endValue:(CGFloat)end duration:(NSTimeInterval)duration curve:(NSAnimationCurve)curve { self = [super initWithDuration:duration animationCurve:curve]; if (self) { self.target = target; self.setter = setter; self.startValue = start; self.endValue = end; self.animationBlockingMode = NSAnimationNonblocking; // 非阻塞模式 } return self; } - (void)startAnimation { [super startAnimation]; // 用CADisplayLink驱动动画进度更新 CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimationProgress:)]; [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; self.representedObject = link; } - (void)stopAnimation { [super stopAnimation]; CADisplayLink *link = self.representedObject; [link invalidate]; self.representedObject = nil; } - (void)updateAnimationProgress:(CADisplayLink *)link { if (!self.isAnimating) { [link invalidate]; return; } // 获取缓动后的进度(0~1) CGFloat progress = [self currentProgress]; // 计算当前值 CGFloat currentValue = self.startValue + (self.endValue - self.startValue) * progress; // 调用target的setter方法更新属性 [self.target performSelector:self.setter withObject:@(currentValue)]; // 进度到1时停止动画 if (progress >= 1.0) { [self stopAnimation]; } } @end // 使用自定义动画 CustomPropertyAnimation *anim = [[CustomPropertyAnimation alloc] initWithTarget:myView setter:@selector(setAlphaValue:) startValue:1.0 endValue:0.0 duration:0.5 curve:NSAnimationCurveEaseIn]; [anim startAnimation];
这个自定义类可以复用在任何支持setter的属性上,非常灵活。
内容的提问来源于stack exchange,提问作者mashers

