我正在处理在父窗口中显示为子窗口的自定义窗口对象。
对于这个对象,我想创建一个类似于 NSPopover 的动画。
我的第一个想法是创建子窗口的屏幕截图,然后使用核心动画对其进行动画处理,最后显示真实的窗口。
在乞求实施之前,我想知道是否存在更好的方法以及您对我的解决方案的看法。
我正在处理在父窗口中显示为子窗口的自定义窗口对象。
对于这个对象,我想创建一个类似于 NSPopover 的动画。
我的第一个想法是创建子窗口的屏幕截图,然后使用核心动画对其进行动画处理,最后显示真实的窗口。
在乞求实施之前,我想知道是否存在更好的方法以及您对我的解决方案的看法。
这不是微不足道的。这是我的做法:
@interface ZoomWindow : NSWindow
{
CGFloat animationTimeMultiplier;
}
@property (nonatomic, readwrite, assign) CGFloat animationTimeMultiplier;
@end
@implementation ZoomWindow
@synthesize animationTimeMultiplier;
- (NSTimeInterval)animationResizeTime: (NSRect)newWindowFrame
{
float multiplier = animationTimeMultiplier;
if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
multiplier *= 10;
}
return [super animationResizeTime: newWindowFrame] * multiplier;
}
@end
@implementation NSWindow (PecuniaAdditions)
- (ZoomWindow*)createZoomWindowWithRect: (NSRect)rect
{
// Code mostly from http://www.noodlesoft.com/blog/2007/06/30/animation-in-the-time-of-tiger-part-1/
// Copyright 2007 Noodlesoft, L.L.C.. All rights reserved.
// The code is provided under the MIT license.
// The code has been extended to support layer-backed views. However, only the top view is
// considered here. The code might not produce the desired output if only a subview has its layer
// set. So better set it on the top view (which should cover most cases).
NSImageView *imageView;
NSImage *image;
NSRect frame;
BOOL isOneShot;
frame = [self frame];
isOneShot = [self isOneShot];
if (isOneShot) {
[self setOneShot: NO];
}
BOOL hasLayer = [[self contentView] wantsLayer];
if ([self windowNumber] <= 0) // <= 0 if hidden
{
// We need to temporarily switch off the backing layer of the content view or we get
// context errors on the second or following runs of this code.
[[self contentView] setWantsLayer: NO];
// Force window device. Kinda crufty but I don't see a visible flash
// when doing this. May be a timing thing wrt the vertical refresh.
[self orderBack: self];
[self orderOut: self];
[[self contentView] setWantsLayer: hasLayer];
}
// Capture the window into an off-screen bitmap.
image = [[NSImage alloc] initWithSize: frame.size];
[[self contentView] lockFocus];
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: NSMakeRect(0.0, 0.0, frame.size.width, frame.size.height)];
[[self contentView] unlockFocus];
[image addRepresentation: rep];
// If the content view is layer-backed the above initWithFocusedViewRect call won't get the content
// of the view (seems it doesn't work for CALayers). So we need a second call that captures the
// CALayer content and copies it over the captured image (compositing so the window frame and its content).
if (hasLayer)
{
NSRect contentFrame = [[self contentView] bounds];
int bitmapBytesPerRow = 4 * contentFrame.size.width;
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGContextRef context = CGBitmapContextCreate (NULL,
contentFrame.size.width,
contentFrame.size.height,
8,
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
[[[self contentView] layer] renderInContext: context];
CGImageRef img = CGBitmapContextCreateImage(context);
CFRelease(context);
NSImage *subImage = [[NSImage alloc] initWithCGImage: img size: contentFrame.size];
CFRelease(img);
[image lockFocus];
[subImage drawAtPoint: NSMakePoint(0, 0)
fromRect: NSMakeRect(0, 0, contentFrame.size.width, contentFrame.size.height)
operation: NSCompositeCopy
fraction: 1];
[image unlockFocus];
}
ZoomWindow *zoomWindow = [[ZoomWindow alloc] initWithContentRect: rect
styleMask: NSBorderlessWindowMask
backing: NSBackingStoreBuffered
defer: NO];
zoomWindow.animationTimeMultiplier = 0.3;
[zoomWindow setBackgroundColor: [NSColor colorWithDeviceWhite: 0.0 alpha: 0.0]];
[zoomWindow setHasShadow: [self hasShadow]];
[zoomWindow setLevel: [self level]];
[zoomWindow setOpaque: NO];
[zoomWindow setReleasedWhenClosed: NO];
[zoomWindow useOptimizedDrawing: YES];
imageView = [[NSImageView alloc] initWithFrame: [zoomWindow contentRectForFrameRect: frame]];
[imageView setImage: image];
[imageView setImageFrameStyle: NSImageFrameNone];
[imageView setImageScaling: NSScaleToFit];
[imageView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
[zoomWindow setContentView: imageView];
[self setOneShot: isOneShot];
return zoomWindow;
}
- (void)fadeIn
{
[self setAlphaValue: 0.f];
[self orderFront: nil];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: 0.3];
[[self animator] setAlphaValue: 1.f];
[NSAnimationContext endGrouping];
}
- (void)zoomInWithOvershot: (NSRect)overshotFrame withFade: (BOOL)fade makeKey: (BOOL)makeKey
{
[self setAlphaValue: 0];
NSRect frame = [self frame];
ZoomWindow *zoomWindow = [self createZoomWindowWithRect: frame];
zoomWindow.alphaValue = 0;
[zoomWindow orderFront: self];
NSDictionary *windowResize = @{NSViewAnimationTargetKey: zoomWindow,
NSViewAnimationEndFrameKey: [NSValue valueWithRect: overshotFrame],
NSViewAnimationEffectKey: NSViewAnimationFadeInEffect};
NSArray *animations = @[windowResize];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations: animations];
[animation setAnimationBlockingMode: NSAnimationBlocking];
[animation setAnimationCurve: NSAnimationEaseIn];
[animation setDuration: 0.2];
[animation startAnimation];
zoomWindow.animationTimeMultiplier = 0.5;
[zoomWindow setFrame: frame display: YES animate: YES];
[self setAlphaValue: 1];
if (makeKey) {
[self makeKeyAndOrderFront: self];
} else {
[self orderFront: self];
}
[zoomWindow close];
}
这是在 NSWindow 类别中实现的。所以你可以打电话:
- (void)zoomInWithOvershot: (NSRect)overshotFrame withFade: (BOOL)fade makeKey: (BOOL)makeKey
在任何 NSWindow 上。
我应该补充一点,我无法让两个动画同时运行(淡入淡出和大小),但效果与 NSPopover 的效果非常相似。也许其他人可以解决动画问题。
不用说,这段代码也适用于没有 NSPopover 的 10.6(这就是我首先编写它的原因)。