Виджет "перевернуть" поведение в Core Animation / Cocoa - PullRequest
8 голосов
/ 16 декабря 2008

Я пытаюсь создать класс Card, который бы дублировал поведение виджетов Dashboard, чтобы вы могли размещать элементы управления или изображения или что-то еще на двух сторонах карты и переключаться между ними.

Виды с резервной копией слоя имеют свойство transform, но изменение того, что не делает того, чего я ожидал (вращение слоя вокруг оси y сворачивает его в левую сторону).

Мне указали на некоторые недокументированные функции и файл .h с именем cgsprivate.h, но мне интересно, есть ли официальный способ сделать это? Это программное обеспечение должно было быть отправлено, и я не хотел бы видеть, что это терпит неудачу позже, потому что парни Apple тянут это в 10.6.

У кого-нибудь есть идеи, как это сделать? Для меня так странно, что в Core Animation так сложно сделать простой виджет.

Заранее спасибо!

РЕДАКТИРОВАТЬ: я могу выполнить это поведение с изображениями, которые находятся на слоях, но я не знаю, как получить более сложные элементы управления / представления / что угодно на слоях. Пример карты использует изображения.

Ответы [ 7 ]

11 голосов
/ 16 декабря 2008

У Майка есть реализация эффекта переворота , для которой он выпустил пример кода . (К сожалению, он больше не доступен в Интернете, но Дрю МакКормак построил это в своей собственной реализации .) Похоже, что он берет слои для «фонового» и «переднего» представлений, которые нужно поменять местами, использует CATransform3D для вращения двух представлений в анимации и затем меняет местами представления после завершения анимации.

Используя слои из представлений, вы избегаете необходимости кэшировать в растровое изображение, так как именно это и делают слои в любом случае. В любом случае, его контроллер представления выглядит как хорошее решение для того, что вы хотите.

6 голосов
/ 22 февраля 2011

Использование базовой анимации, как описано в e.James ... Обратите внимание, это использует сборку мусора и размещенный слой:

#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

Заголовочный файл:

@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

РЕДАКТИРОВАТЬ: это будет анимировать, чтобы перевернуть из одного окна в другое окно. Вы можете применять те же принципы к представлению.

3 голосов
/ 16 декабря 2008

Это излишне для ваших целей (поскольку оно содержит в основном полное справочное приложение для настольных и карточных игр), но посмотрите этот образец из ADC Карточные игры, входящие в комплект, прекрасно справляются с этой задачей.

2 голосов
/ 16 декабря 2008

Если вы можете сделать это с изображениями, возможно, вы можете сохранить все свои элементы управления в объекте NSView (как обычно), а затем преобразовать NSView в растровое изображение, используя cacheDisplayInRect:toBitmapImageRep: непосредственно перед выполнением эффекта отражения. Шаги будут:

  1. Визуализация NSView в растровое изображение
  2. Отображение этого растрового изображения в слое, подходящем для эффекта отражения
  3. Скрыть NSView и выставить слой изображения
  4. Выполнить эффект отражения
1 голос
/ 03 апреля 2013

Я знаю, что уже поздно, но у Apple есть пример проекта, который может помочь любому, кто все еще сталкивается с этим вопросом.

https://developer.apple.com/library/mac/#samplecode/ImageTransition/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010277

0 голосов
/ 31 января 2015

Вероятно, не тот случай, когда в 2008 году задавался этот вопрос, но в наши дни это довольно просто:

[UIView animateWithDuration:0.5 animations:^{
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.iconView cache:YES];
    /* changes to the view made here will be reflected on the flipped to side */
}];

Примечание. По-видимому, это работает только на iOS.

0 голосов
/ 27 октября 2011

Ребята из Mizage предлагают полную реализацию с открытым исходным кодом.

Вы можете проверить это здесь: https://github.com/mizage/Flip-Animation

...