Widget “flip” behavior in Core Animation/Cocoa

前端 未结 7 1887
执笔经年
执笔经年 2020-11-30 02:31

I\'m trying to make a Card class that duplicates the behavior of Dashboard widgets in that you can put controls or images or whatever on two sides of the card and flip betwe

相关标签:
7条回答
  • 2020-11-30 03:33

    Using Core Animation like e.James outlined...Note, this is using garbage collection and a hosted layer:

    #import "AnimationWindows.h"
    
    @interface AnimationFlipWindow (PrivateMethods)
    
    NSRect RectToScreen(NSRect aRect, NSView *aView);
    NSRect RectFromScreen(NSRect aRect, NSView *aView);
    NSRect RectFromViewToView(NSRect aRect, NSView *fromView, NSView *toView);
    
    @end
    
    #pragma mark -
    
    @implementation AnimationFlipWindow
    
    @synthesize flipForward = _flipForward;
    
    - (id) init {
    
        if ( self = [super init] ) { 
            _flipForward = YES; 
        }
    
        return self;
    }
    
    - (void) finalize {
    
        // Hint to GC for cleanup
        [[NSGarbageCollector defaultCollector] collectIfNeeded];
        [super finalize];
    }
    
    - (void) flip:(NSWindow *)activeWindow 
           toBack:(NSWindow *)targetWindow {
    
        CGFloat duration = 1.0f * (activeWindow.currentEvent.modifierFlags & NSShiftKeyMask ? 10.0 : 1.0);
        CGFloat zDistance = 1500.0f;
    
        NSView *activeView = [activeWindow.contentView superview];
        NSView *targetView = [targetWindow.contentView superview];
    
        // Create an animation window
        CGFloat maxWidth  = MAX(NSWidth(activeWindow.frame), NSWidth(targetWindow.frame)) + 500;
        CGFloat maxHeight = MAX(NSHeight(activeWindow.frame), NSHeight(targetWindow.frame)) + 500;
    
        CGRect animationFrame = CGRectMake(NSMidX(activeWindow.frame) - (maxWidth / 2), 
                                           NSMidY(activeWindow.frame) - (maxHeight / 2), 
                                           maxWidth, 
                                           maxHeight);
    
        mAnimationWindow = [NSWindow initForAnimation:NSRectFromCGRect(animationFrame)];
    
        // Add a touch of perspective
        CATransform3D transform = CATransform3DIdentity; 
        transform.m34 = -1.0 / zDistance;
        [mAnimationWindow.contentView layer].sublayerTransform = transform;
    
        // Relocate target window near active window
        CGRect targetFrame = CGRectMake(NSMidX(activeWindow.frame) - (NSWidth(targetWindow.frame) / 2 ), 
                                        NSMaxY(activeWindow.frame) - NSHeight(targetWindow.frame),
                                        NSWidth(targetWindow.frame),
                                        NSHeight(targetWindow.frame));
    
        [targetWindow setFrame:NSRectFromCGRect(targetFrame) display:NO];
    
        mTargetWindow = targetWindow;
    
        // New Active/Target Layers
        [CATransaction begin];
        CALayer *activeWindowLayer = [activeView layerFromWindow];
        CALayer *targetWindowLayer = [targetView layerFromWindow];
        [CATransaction commit];
    
        activeWindowLayer.frame = NSRectToCGRect(RectFromViewToView(activeView.frame, activeView, [mAnimationWindow contentView]));
        targetWindowLayer.frame = NSRectToCGRect(RectFromViewToView(targetView.frame, targetView, [mAnimationWindow contentView]));
    
        [CATransaction begin];
        [[mAnimationWindow.contentView layer] addSublayer:activeWindowLayer];
        [CATransaction commit];
    
        [mAnimationWindow orderFront:nil];  
    
        [CATransaction begin];
        [[mAnimationWindow.contentView layer] addSublayer:targetWindowLayer];
        [CATransaction commit];
    
        // Animate our new layers
        [CATransaction begin];
        CAAnimation *activeAnim = [CAAnimation animationWithDuration:(duration * 0.5) flip:YES forward:_flipForward];
        CAAnimation *targetAnim = [CAAnimation animationWithDuration:(duration * 0.5) flip:NO  forward:_flipForward];
        [CATransaction commit];
    
        targetAnim.delegate = self;
        [activeWindow orderOut:nil];
    
        [CATransaction begin];
        [activeWindowLayer addAnimation:activeAnim forKey:@"flip"];
        [targetWindowLayer addAnimation:targetAnim forKey:@"flip"];
        [CATransaction commit];
    }
    
    - (void) animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
    
        if (flag) {
            [mTargetWindow makeKeyAndOrderFront:nil];
            [mAnimationWindow orderOut:nil];
    
            mTargetWindow = nil;
            mAnimationWindow = nil;
        }
    }
    
    #pragma mark PrivateMethods:
    
    NSRect RectToScreen(NSRect aRect, NSView *aView) {
        aRect = [aView convertRect:aRect toView:nil];
        aRect.origin = [aView.window convertBaseToScreen:aRect.origin];
        return aRect;
    }
    
    NSRect RectFromScreen(NSRect aRect, NSView *aView) {
        aRect.origin = [aView.window convertScreenToBase:aRect.origin];
        aRect = [aView convertRect:aRect fromView:nil];
        return aRect;
    }
    
    NSRect RectFromViewToView(NSRect aRect, NSView *fromView, NSView *toView) {
    
        aRect = RectToScreen(aRect, fromView);
        aRect = RectFromScreen(aRect, toView);
    
        return aRect;
    }
    
    @end
    
    #pragma mark -
    #pragma mark CategoryMethods:
    
    @implementation CAAnimation (AnimationFlipWindow)
    
    + (CAAnimation *) animationWithDuration:(CGFloat)time flip:(BOOL)bFlip forward:(BOOL)forwardFlip{
    
        CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    
        CGFloat startValue, endValue;
    
        if ( forwardFlip ) {
            startValue = bFlip ? 0.0f : -M_PI;
            endValue = bFlip ? M_PI : 0.0f;
        } else {
            startValue = bFlip ? 0.0f : M_PI;
            endValue = bFlip ? -M_PI : 0.0f;
        }
    
        flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
        flipAnimation.toValue = [NSNumber numberWithDouble:endValue];
    
        CABasicAnimation *shrinkAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        shrinkAnimation.toValue = [NSNumber numberWithFloat:1.3f];
        shrinkAnimation.duration = time * 0.5;
        shrinkAnimation.autoreverses = YES;
    
        CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
        animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, shrinkAnimation, nil];
        animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        animationGroup.duration = time;
        animationGroup.fillMode = kCAFillModeForwards;
        animationGroup.removedOnCompletion = NO;
    
        return animationGroup;
    }
    
    @end
    
    #pragma mark -
    
    @implementation NSWindow (AnimationFlipWindow)
    
    + (NSWindow *) initForAnimation:(NSRect)aFrame {
    
        NSWindow *window =  [[NSWindow alloc] initWithContentRect:aFrame 
                                                        styleMask:NSBorderlessWindowMask 
                                                          backing:NSBackingStoreBuffered 
                                                            defer:NO];
        [window setOpaque:NO];
        [window setHasShadow:NO];
        [window setBackgroundColor:[NSColor clearColor]];
        [window.contentView setWantsLayer:YES];
    
        return window;
    }
    
    @end
    
    #pragma mark -
    
    @implementation NSView (AnimationFlipWindow)
    
    - (CALayer *) layerFromWindow {
    
        NSBitmapImageRep *image = [self bitmapImageRepForCachingDisplayInRect:self.bounds];
        [self cacheDisplayInRect:self.bounds toBitmapImageRep:image];
    
        CALayer *layer = [CALayer layer];
        layer.contents = (id)image.CGImage;
        layer.doubleSided = NO;
    
        // Shadow settings based upon Mac OS X 10.6
        [layer setShadowOpacity:0.5f];
        [layer setShadowOffset:CGSizeMake(0,-10)];
        [layer setShadowRadius:15.0f];
    
    
        return layer;
    }
    
    @end
    

    The header file:

    @interface AnimationFlipWindow : NSObject {
    
        BOOL _flipForward;
    
        NSWindow *mAnimationWindow;
        NSWindow *mTargetWindow;
    }
    
    // Direction of flip animation (property)
    @property (readwrite, getter=isFlipForward) BOOL flipForward;
    
    - (void) flip:(NSWindow *)activeWindow 
           toBack:(NSWindow *)targetWindow;
    @end
    
    #pragma mark -
    #pragma mark CategoryMethods:
    
    @interface CAAnimation (AnimationFlipWindow)
    + (CAAnimation *) animationWithDuration:(CGFloat)time 
                                       flip:(BOOL)bFlip // Flip for each side
                                    forward:(BOOL)forwardFlip; // Direction of flip
    @end
    
    @interface NSWindow (AnimationFlipWindow)
    + (NSWindow *) initForAnimation:(NSRect)aFrame;
    @end
    
    @interface NSView (AnimationFlipWindow)
    - (CALayer *) layerFromWindow;
    @end
    

    EDIT: This will animate to flip from one window to another window. You can apply the same principals to a view.

    0 讨论(0)
提交回复
热议问题