高级绘图与动画技术详解
1. 曲线绘制
在进行曲线绘制时,为了便于调整外观,我们使用预处理器
#define
来定义颜色和线宽的值。在
CECurveView.m
文件顶部添加以下代码:
#define CP_RADIUS 0.1
#define CP_DIAMETER (CP_RADIUS*2)
#define BACKGROUND_COLOR [NSColor whiteColor]
#define GRID_STROKE_COLOR [NSColor lightGrayColor]
#define GRID_FILL_COLOR [NSColor colorWithCalibratedWhite:0.9 alpha:1.0]
#define CURVE_COLOR [NSColor blackColor]
#define LINE_TO_CP_COLOR [NSColor darkGrayColor]
#define CP_GRADIENT_COLOR1 [NSColor lightGrayColor]
#define CP_GRADIENT_COLOR2 [NSColor darkGrayColor]
接着,我们实现
drawControlPointAtX:y:
和
drawRect:
方法,示例代码如下:
- (void)drawControlPointAtX:(CGFloat)x y:(CGFloat)y {
NSBezierPath *cp = [NSBezierPath bezierPathWithOvalInRect:
NSMakeRect(x - CP_RADIUS, y - CP_RADIUS,
CP_DIAMETER, CP_DIAMETER)];
NSGradient *g;
g = [[NSGradient alloc] initWithStartingColor:CP_GRADIENT_COLOR1
endingColor:CP_GRADIENT_COLOR2];
[g drawInBezierPath:cp
relativeCenterPosition:NSMakePoint(0.0, 0.0)];
}
- (void)drawRect:(NSRect)rect {
[NSGraphicsContext saveGraphicsState];
// draw the background
NSBezierPath *bg = [NSBezierPath bezierPathWithRoundedRect:myBounds
xRadius:0.1 yRadius:0.1];
[BACKGROUND_COLOR set];
[bg fill];
// draw the grid
NSBezierPath *grid1 = [NSBezierPath bezierPath];
[grid1 moveToPoint:NSMakePoint(0.0, 0.0)];
[grid1 lineToPoint:NSMakePoint(1.0, 0.0)];
[grid1 lineToPoint:NSMakePoint(1.0, 1.0)];
[grid1 lineToPoint:NSMakePoint(0.0, 1.0)];
[grid1 lineToPoint:NSMakePoint(0.0, 0.0)];
[grid1 moveToPoint:NSMakePoint(0.5, 0.0)];
[grid1 lineToPoint:NSMakePoint(0.5, 1.0)];
[grid1 moveToPoint:NSMakePoint(0.0, 0.5)];
[grid1 lineToPoint:NSMakePoint(1.0, 0.5)];
[GRID_FILL_COLOR set];
[grid1 fill];
[GRID_STROKE_COLOR set];
[grid1 setLineWidth:0.01];
[grid1 stroke];
// draw the lines leading to the control points
NSBezierPath *cpLines = [NSBezierPath bezierPath];
[cpLines moveToPoint:NSMakePoint(0.0, 0.0)];
[cpLines lineToPoint:NSMakePoint(self.cp1X, self.cp1Y)];
[cpLines moveToPoint:NSMakePoint(1.0, 1.0)];
[cpLines lineToPoint:NSMakePoint(self.cp2X, self.cp2Y)];
[LINE_TO_CP_COLOR set];
[cpLines setLineWidth:0.01];
[cpLines stroke];
// draw the curve itself
NSBezierPath *bp = [NSBezierPath bezierPath];
[bp moveToPoint:NSMakePoint(0.0, 0.0)];
[bp curveToPoint:NSMakePoint(1.0, 1.0)
controlPoint1:NSMakePoint(self.cp1X, self.cp1Y)
controlPoint2:NSMakePoint(self.cp2X, self.cp2Y)];
[CURVE_COLOR set];
[bp setLineWidth:0.01];
[bp stroke];
// draw the control points
[self drawControlPointAtX:self.cp1X y:self.cp1Y];
[self drawControlPointAtX:self.cp2X y:self.cp2Y];
[NSGraphicsContext restoreGraphicsState];
}
这种绘制代码虽然方法较长,但逻辑简单,没有循环和
if
语句。运行应用程序后,我们可以编辑文本字段中的值(范围在 0.0 到 1.0 之间),控制控制点和曲线的变化。
2. 鼠标交互
为了实现控制点的拖动功能,我们需要添加一些属性和方法来处理鼠标事件。
-
添加属性
:在
CECurveView.h
的
@interface
声明中添加以下两行代码:
@property (assign) BOOL draggingCp1;
@property (assign) BOOL draggingCp2;
- 实现鼠标事件方法 :
-
mouseDown:方法 :当用户点击视图时调用,用于判断点击位置是否在控制点上。
- (void)mouseDown:(NSEvent *)theEvent {
// get current mouse location, convert to our coordinate space
// (the one expresed by our bounds)
NSPoint mouseLocation = [theEvent locationInWindow];
NSPoint convertedLocation = [self convertPoint:mouseLocation
fromView:nil];
// see if the click was on one of our control knobs
NSPoint cp1 = NSMakePoint(self.cp1X, self.cp1Y);
NSPoint cp2 = NSMakePoint(self.cp2X, self.cp2Y);
if (pointsWithinDistance(self.cp1, convertedLocation, CP_RADIUS)) {
self.draggingCp1 = YES;
} else if (pointsWithinDistance(cp2, convertedLocation, CP_RADIUS)){
self.draggingCp2 = YES;
}
[self setNeedsDisplay:YES];
}
其中,
pointsWithinDistance
函数用于判断两点之间的距离是否小于指定值,代码如下:
static BOOL pointsWithinDistance(NSPoint p1, NSPoint p2, CGFloat d) {
return pow((p1.x-p2.x), 2) + pow((p1.y - p2.y), 2) <= pow(d, 2);
}
-
mouseDragged:方法 :当鼠标拖动时调用,更新当前被拖动的控制点的坐标。
- (void)mouseDragged:(NSEvent *)theEvent {
NSPoint mouseLocation = [theEvent locationInWindow];
NSPoint convertedLocation = [self convertPoint:mouseLocation
fromView:nil];
if (self.draggingCp1) {
self.cp1X = convertedLocation.x;
self.cp1Y = convertedLocation.y;
} else if (self.draggingCp2) {
self.cp2X = convertedLocation.x;
self.cp2Y = convertedLocation.y;
}
[self setNeedsDisplay:YES];
}
-
mouseUp:方法 :当鼠标释放时调用,将拖动标志设置为NO。
- (void)mouseUp:(NSEvent *)theEvent {
self.draggingCp1 = NO;
self.draggingCp2 = NO;
}
运行应用程序后,我们可以拖动控制点,曲线会随之变化,文本字段中的数值也会相应更新。
3. 界面优化
为了提升用户体验,我们对界面进行一些优化。
-
调整控制点绘制顺序
:在
drawRect:
方法末尾添加以下代码,确保拖动第一个控制点时,它显示在第二个控制点前面。
// draw the control points
if (self.draggingCp1) {
[self drawControlPointAtX:self.cp2X y:self.cp2Y];
[self drawControlPointAtX:self.cp1X y:self.cp1Y];
} else {
[self drawControlPointAtX:self.cp1X y:self.cp1Y];
[self drawControlPointAtX:self.cp2X y:self.cp2Y];
}
- 高亮显示拖动的控制点 :
-
定义高亮颜色:在文件顶部的
#define部分添加以下两行代码:
#define CP_GRADIENT_HIGHLIGHT_COLOR1 [NSColor whiteColor]
#define CP_GRADIENT_HIGHLIGHT_COLOR2 [NSColor redColor]
-
修改
drawControlPointAtX:y:方法,添加一个参数来指定是否绘制高亮版本。
- (void)drawControlPointAtX:(CGFloat)x y:(CGFloat)y dragging:(BOOL)dragging {
NSBezierPath *cp = [NSBezierPath bezierPathWithOvalInRect:
NSMakeRect(x - CP_RADIUS, y - CP_RADIUS, CP_DIAMETER, CP_DIAMETER)];
NSGradient *g;
if (dragging) {
g = [[NSGradient alloc] initWithStartingColor:CP_GRADIENT_HIGHLIGHT_COLOR1
endingColor:CP_GRADIENT_HIGHLIGHT_COLOR2];
} else {
g = [[NSGradient alloc] initWithStartingColor:CP_GRADIENT_COLOR1
endingColor:CP_GRADIENT_COLOR2];
}
[g drawInBezierPath:cp
relativeCenterPosition:NSMakePoint(0.0, 0.0)];
}
-
修改
drawRect:方法中调用drawControlPointAtX:y:的方式。
// draw the control points
if (self.draggingCp1) {
[self drawControlPointAtX:self.cp2X y:self.cp2Y dragging:self.draggingCp2];
[self drawControlPointAtX:self.cp1X y:self.cp1Y dragging:self.draggingCp1];
} else {
[self drawControlPointAtX:self.cp1X y:cp1Y dragging:self.draggingCp1];
[self drawControlPointAtX:self.cp2X y:self.cp2Y dragging:self.draggingCp2];
}
-
在
mouseUp:方法末尾添加[self setNeedsDisplay:YES];,解决释放鼠标后控制点仍高亮的问题。
4. Core Animation 基础
Core Animation 是苹果公司在 Mac OS X 中提供的图形系统,它可以让我们轻松创建动画效果,如视图的滑动、淡入淡出、旋转和缩放等。其基本单位是
CALayer
,每个
NSView
可以选择关联一个
CALayer
,关联后,视图及其所有子视图都会获得图层。
CALayer
与 OpenGL 结构相关联,能快速绘制图形,且 Core Animation 的 API 屏蔽了 OpenGL 的复杂性。不过,每个图层会占用一定的图形硬件内存,因此建议仅在需要动画的部分使用图层。
5. 隐式动画
任何具有图层支持的视图都可以使用其动画代理进行隐式动画。例如,要动画视图的移动,我们可以使用以下代码:
[[myView animator] setFrame:newFrame];
而不是直接设置框架:
[myView setFrame:newFrame];
为了演示隐式动画,我们创建一个名为
MovingButton
的新 Cocoa 项目,并按以下步骤操作:
1.
设置用户界面
:在 Interface Builder 中打开
MainMenu.xib
,在
MovingButton
窗口中添加一个名为
Move
的按钮。
2.
添加动作
:打开助理编辑器,将按钮拖到
MBAppDelegate.h
文件中,添加一个名为
move
的动作。
3.
实现动作方法
:在
MBAppDelegate.m
文件中实现
move:
方法。
- (IBAction)move:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
senderFrame.origin.x = (superBounds.size.width -
senderFrame.size.width) * drand48();
senderFrame.origin.y = (superBounds.size.height -
senderFrame.size.height) * drand48();
[sender setFrame:senderFrame];
}
保存并运行项目,点击
Move
按钮,按钮会直接跳到新位置。将
[sender setFrame:senderFrame];
改为
[[sender animator] setFrame:senderFrame];
后,再次运行项目,点击按钮,按钮会平滑地滑动到新位置。
我们还可以通过
NSAnimationContext
设置隐式动画的持续时间:
[[NSAnimationContext currentContext] setDuration:1.0];
6. 显式动画
对于需要更精细控制的动画,我们可以使用显式动画。使用显式动画前,需要在 Xcode 项目中添加
QuartzCore
框架,并在
MBAppDelegate.m
文件顶部导入头文件:
#import <QuartzCore/QuartzCore.h>
修改
move:
方法,使用
CABasicAnimation
进行显式动画:
- (IBAction)move:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
CABasicAnimation *a = [CABasicAnimation
animationWithKeyPath:@"position"];
a.fromValue = [NSValue valueWithPoint:senderFrame.origin];
senderFrame.origin.x = (superBounds.size.width -
senderFrame.size.width)*drand48();
senderFrame.origin.y = (superBounds.size.height -
senderFrame.size.height)*drand48();
a.toValue = [NSValue valueWithPoint:senderFrame.origin];
[[sender layer] addAnimation:a forKey:@"position"];
[sender setFrame:senderFrame];
}
在运行此代码前,需要手动为需要动画的视图及其父视图设置图层。具体操作如下:
1. 打开
MainMenu.xib
,选择按钮。
2. 打开视图效果检查器(
⌘⌥8
)。
3. 在“Core Animation Layer”部分,勾选父视图(窗口的内容视图)的图层复选框。
运行应用程序后,按钮的动画效果与隐式动画相同,但显式动画提供了更多控制选项。例如,我们可以设置动画的持续时间和节奏:
a.duration = 1.0;
a.timingFunction = [CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseInEaseOut];
为了使用曲线编辑器定义按钮移动的节奏,我们可以按以下步骤操作:
1. 在
MovingButton
项目中添加
CECurveView
类文件。
2. 修改
MBAppDelegate.h
文件,添加一个
CECurveView
的输出口。
#import <Cocoa/Cocoa.h>
@class CECurveView;
@interface MBAppDelegate : NSObject <NSApplicationDelegate>
@property (assign) IBOutlet NSWindow *window;
@property (weak) IBOutlet CECurveView *curveView;
- (IBAction)move:(id)sender;
@end
-
在 Interface Builder 中添加一个
NSPanel和一个CECurveView,并将输出口连接到CECurveView实例。 -
在
MBAppDelegate.m文件顶部导入CECurveView.h文件。 -
更新
move:方法,使用曲线编辑器的控制点定义动画的节奏。
- (IBAction)move:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
CABasicAnimation *a = [CABasicAnimation
animationWithKeyPath:@"position"];
a.fromValue = [NSValue valueWithPoint:senderFrame.origin];
senderFrame.origin.x = (superBounds.size.width -
senderFrame.size.width)*drand48();
senderFrame.origin.y = (superBounds.size.height -
senderFrame.size.height)*drand48();
a.toValue = [NSValue valueWithPoint:senderFrame.origin];
a.duration = 1.0;
a.timingFunction = [CAMediaTimingFunction
functionWithControlPoints:self.curveView.cp1X
:self.curveView.cp1Y
通过以上步骤,我们可以实现曲线绘制、鼠标交互、界面优化以及 Core Animation 的隐式和显式动画,提升应用程序的用户体验和视觉效果。
高级绘图与动画技术详解
7. 显式动画的应用与拓展
显式动画在实际应用中具有更多的灵活性和可定制性。除了前面提到的设置动画的持续时间和节奏,我们还可以利用显式动画实现更复杂的效果。
7.1 动画分组
有时候,我们需要多个动画同时执行,以达到更丰富的视觉效果。Core Animation 提供了
CAAnimationGroup
类来实现动画分组。以下是一个简单的示例,展示如何将位置动画和透明度动画组合在一起:
- (IBAction)moveAndFade:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
// 位置动画
CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
positionAnimation.fromValue = [NSValue valueWithPoint:senderFrame.origin];
senderFrame.origin.x = (superBounds.size.width - senderFrame.size.width) * drand48();
senderFrame.origin.y = (superBounds.size.height - senderFrame.size.height) * drand48();
positionAnimation.toValue = [NSValue valueWithPoint:senderFrame.origin];
// 透明度动画
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.fromValue = @1.0;
opacityAnimation.toValue = @0.2;
// 创建动画组
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[positionAnimation, opacityAnimation];
animationGroup.duration = 2.0;
[[sender layer] addAnimation:animationGroup forKey:@"moveAndFade"];
[sender setFrame:senderFrame];
}
在这个示例中,我们创建了一个
CAAnimationGroup
对象,并将位置动画和透明度动画添加到该组中。然后将动画组添加到视图的图层上,这样两个动画就会同时执行。
7.2 动画的委托与回调
在某些情况下,我们需要在动画开始或结束时执行一些特定的操作。Core Animation 允许我们为动画设置委托,并实现相应的委托方法。以下是一个示例,展示如何在动画结束时执行回调:
@interface MBAppDelegate () <CAAnimationDelegate>
@end
@implementation MBAppDelegate
- (IBAction)moveWithCallback:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithPoint:senderFrame.origin];
senderFrame.origin.x = (superBounds.size.width - senderFrame.size.width) * drand48();
senderFrame.origin.y = (superBounds.size.height - senderFrame.size.height) * drand48();
animation.toValue = [NSValue valueWithPoint:senderFrame.origin];
animation.duration = 1.0;
animation.delegate = self;
[[sender layer] addAnimation:animation forKey:@"moveWithCallback"];
[sender setFrame:senderFrame];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (flag) {
NSLog(@"Animation finished!");
// 在这里可以执行动画结束后的操作
}
}
@end
在这个示例中,我们为动画设置了委托,并实现了
animationDidStop:finished:
方法。当动画结束时,该方法会被调用,我们可以在其中执行一些特定的操作,如更新界面或触发其他动画。
8. 综合应用:曲线编辑器与动画的结合
前面我们介绍了曲线编辑器和 Core Animation 的基本用法,现在我们将两者结合起来,实现一个更复杂的应用。
8.1 曲线编辑器的优化
为了更好地与动画结合,我们可以对曲线编辑器进行一些优化。例如,添加更多的交互功能,如保存和加载曲线设置。
// 在 CECurveView.h 中添加保存和加载方法
@interface CECurveView : NSView
- (void)saveCurveSettings;
- (void)loadCurveSettings;
@end
// 在 CECurveView.m 中实现保存和加载方法
@implementation CECurveView
- (void)saveCurveSettings {
// 保存控制点的坐标到文件或用户偏好设置
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setFloat:self.cp1X forKey:@"cp1X"];
[defaults setFloat:self.cp1Y forKey:@"cp1Y"];
[defaults setFloat:self.cp2X forKey:@"cp2X"];
[defaults setFloat:self.cp2Y forKey:@"cp2Y"];
[defaults synchronize];
}
- (void)loadCurveSettings {
// 从文件或用户偏好设置中加载控制点的坐标
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.cp1X = [defaults floatForKey:@"cp1X"];
self.cp1Y = [defaults floatForKey:@"cp1Y"];
self.cp2X = [defaults floatForKey:@"cp2X"];
self.cp2Y = [defaults floatForKey:@"cp2Y"];
[self setNeedsDisplay:YES];
}
@end
8.2 利用曲线编辑器控制动画节奏
我们可以使用曲线编辑器来定义按钮移动的节奏。在前面的示例中,我们已经实现了基本的曲线编辑器与动画的结合。现在,我们可以进一步优化代码,使其更加灵活。
- (IBAction)moveWithCurve:(id)sender {
NSRect senderFrame = [sender frame];
NSRect superBounds = [[sender superview] bounds];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithPoint:senderFrame.origin];
senderFrame.origin.x = (superBounds.size.width - senderFrame.size.width) * drand48();
senderFrame.origin.y = (superBounds.size.height - senderFrame.size.height) * drand48();
animation.toValue = [NSValue valueWithPoint:senderFrame.origin];
animation.duration = 1.0;
// 使用曲线编辑器的控制点定义动画的节奏
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:self.curveView.cp1X
:self.curveView.cp1Y
:self.curveView.cp2X
:self.curveView.cp2Y];
[[sender layer] addAnimation:animation forKey:@"moveWithCurve"];
[sender setFrame:senderFrame];
}
通过这种方式,我们可以根据曲线编辑器的设置动态调整动画的节奏,实现更加个性化的动画效果。
9. 总结
本文详细介绍了曲线绘制、鼠标交互、界面优化以及 Core Animation 的基本用法和高级应用。通过这些技术,我们可以创建出更加丰富、生动的用户界面和动画效果。
-
曲线绘制
:使用预处理器
#define定义颜色和线宽,通过NSBezierPath绘制曲线和控制点。 -
鼠标交互
:通过重写
mouseDown:、mouseDragged:和mouseUp:方法,实现控制点的拖动功能。 - 界面优化 :调整控制点的绘制顺序和高亮显示拖动的控制点,提升用户体验。
- Core Animation :介绍了隐式动画和显式动画的用法,以及动画分组、委托与回调等高级特性。
- 综合应用 :将曲线编辑器与动画结合,实现更加个性化的动画效果。
希望这些内容对大家在开发 Mac OS X 应用时有所帮助。在实际应用中,我们可以根据具体需求灵活运用这些技术,创造出更加出色的应用程序。
相关操作步骤总结
| 操作 | 步骤 |
|---|---|
| 曲线绘制 |
1. 在
CECurveView.m
文件顶部定义颜色和线宽。
2. 实现
drawControlPointAtX:y:
和
drawRect:
方法。
|
| 鼠标交互 |
1. 在
CECurveView.h
中添加拖动标志属性。
2. 实现
mouseDown:
、
mouseDragged:
和
mouseUp:
方法。
|
| 界面优化 |
1. 调整
drawRect:
方法中控制点的绘制顺序。
2. 定义高亮颜色,修改
drawControlPointAtX:y:
方法。
3. 在
mouseUp:
方法中添加刷新界面的代码。
|
| 隐式动画 |
1. 创建
MovingButton
项目,设置用户界面。
2. 添加
move
动作,使用
[[sender animator] setFrame:newFrame];
实现动画。
|
| 显式动画 |
1. 添加
QuartzCore
框架,导入头文件。
2. 使用
CABasicAnimation
创建动画,添加到视图的图层上。
3. 手动为需要动画的视图及其父视图设置图层。 |
| 动画分组 |
1. 创建多个动画对象。
2. 创建
CAAnimationGroup
对象,将动画添加到组中。
3. 将动画组添加到视图的图层上。 |
| 动画的委托与回调 |
1. 为动画设置委托,实现
CAAnimationDelegate
协议。
2. 实现
animationDidStop:finished:
方法,在动画结束时执行回调。
|
| 曲线编辑器与动画结合 |
1. 优化曲线编辑器,添加保存和加载功能。
2. 使用曲线编辑器的控制点定义动画的节奏。 |
流程图:显式动画实现流程
graph LR
A[创建 MovingButton 项目] --> B[添加 QuartzCore 框架]
B --> C[导入头文件]
C --> D[创建 CABasicAnimation 对象]
D --> E[设置动画的起始和结束值]
E --> F[设置动画的持续时间和节奏]
F --> G[将动画添加到视图的图层上]
G --> H[手动为视图及其父视图设置图层]
H --> I[运行应用程序,查看动画效果]
通过以上的总结和流程图,我们可以更清晰地了解各个技术点的操作步骤和实现流程,方便在实际开发中进行参考和应用。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



