1

我正在处理在父窗口中显示为子窗口的自定义窗口对象。
对于这个对象,我想创建一个类似于 NSPopover 的动画。

我的第一个想法是创建子窗口的屏幕截图,然后使用核心动画对其进行动画处理,最后显示真实的窗口。

在乞求实施之前,我想知道是否存在更好的方法以及您对我的解决方案的看法。

4

1 回答 1

3

这不是微不足道的。这是我的做法:

@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(这就是我首先编写它的原因)。

于 2013-04-20T14:51:48.533 回答