macos Core Animation/Cocoa 中的小部件“翻转”行为
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/372018/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Widget "flip" behavior in Core Animation/Cocoa
提问by Brad Larson
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 between them.
我正在尝试制作一个 Card 类,它复制 Dashboard 小部件的行为,因为您可以在卡片的两侧放置控件或图像或任何内容,并在它们之间翻转。
Layer backed views have a transform property, but altering that doesn't do what I would expect it to do (rotating the layer around the y axis folds it off to the left side).
图层支持的视图有一个转换属性,但是改变它并没有像我期望的那样做(围绕 y 轴旋转图层会将它折叠到左侧)。
I was pointed to some undocumented features and an .h file named cgsprivate.h, but I'm wondering if there is an official way to do this? This software would have to be shipped and I'd hate to see it fail later because the Apple guys pull it in 10.6.
有人指出了一些未记录的功能和一个名为 cgsprivate.h 的 .h 文件,但我想知道是否有官方方法可以做到这一点?该软件将不得不发布,我不想看到它稍后失败,因为 Apple 人员将其拉入 10.6。
Anyone have any idea how to do this? It's so weird to me that a simple widget thing would be so hard to do in Core Animation.
任何人都知道如何做到这一点?对我来说很奇怪,在 Core Animation 中很难做一个简单的小部件。
Thanks in advance!
提前致谢!
EDIT: I can accomplish this behavior with images that are on layers, but I don't know how to get more advanced controls/views/whatever on the layers. The card example uses images.
编辑:我可以使用图层上的图像完成此行为,但我不知道如何获得更高级的控件/视图/图层上的任何内容。卡片示例使用图像。
回答by Brad Larson
Mike Lee has an implementation of the flip effectfor which he has released some sample code.(Unfortunately, this is no longer available online, but Drew McCormack built off of that in his own implementation.) It appears that he grabs the layers for the "background" and "foreground" views to be swapped, uses a CATransform3D to rotate the two views in the animation, and then swaps the views once the animation has completed.
Mike Lee 有一个翻转效果的实现,他已经发布了一些示例代码。(不幸的是,这不再在线可用,但 Drew McCormack在他自己的实现中建立了它。)似乎他抓住了要交换的“背景”和“前景”视图的层,使用 CATransform3D 来旋转动画中的两个视图,然后在动画完成后交换视图。
By using the layers from the views, you avoid needing to cache into a bitmap, since that's what the layers are doing anyways. In any case, his view controller looks to be a good drop-in solution for what you want.
通过使用视图中的图层,您无需缓存到位图中,因为无论如何图层都是这样做的。无论如何,他的视图控制器看起来是您想要的一个很好的直接解决方案。
回答by Arvin
Using Core Animation like e.James outlined...Note, this is using garbage collection and a hosted layer:
使用像 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
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.
编辑:这将动画从一个窗口翻转到另一个窗口。您可以将相同的主体应用于视图。
回答by John Rudy
It's overkill for your purposes (as it contains a largely-complete board and card game reference app), but check out this sample from ADC. The card games included with it do that flip effect quite nicely.
这对您的目的来说太过分了(因为它包含一个基本完整的棋盘和纸牌游戏参考应用程序),但请查看来自 ADC 的此示例。随附的纸牌游戏可以很好地实现翻转效果。
回答by e.James
If you are able to do this with images, perhaps you can keep all of your controls in an NSView
object (as usual), and then render the NSView
into a bitmap image using cacheDisplayInRect:toBitmapImageRep:
just prior to executing the flip effect. The steps would be:
如果您能够对图像执行此操作,也许您可以将所有控件保留在一个NSView
对象中(像往常一样),然后在执行翻转效果之前将其渲染NSView
为位图图像cacheDisplayInRect:toBitmapImageRep:
。步骤是:
- Render the
NSView
to a bitmap - Display that bitmap in a layer suitable for the flip effect
- Hide the
NSView
and expose the image layer - Perform the flip effect
- 渲染
NSView
到位图 - 在适合翻转效果的图层中显示该位图
- 隐藏
NSView
和暴露图像层 - 执行翻转效果
回答by Shantanu
I know this is late but Apple has an example project here that may be of help to anyone still stumbling upon this question.
我知道这已经晚了,但 Apple 在这里有一个示例项目,它可能对仍然遇到这个问题的人有所帮助。
回答by Tyler
There's a complete open source implementation of this by the guys at Mizage.
Mizage 的人有一个完整的开源实现。
You can check it out here: https://github.com/mizage/Flip-Animation
你可以在这里查看:https: //github.com/mizage/Flip-Animation
回答by aepryus
Probably not the case in 2008 when this question was asked, but this is pretty easy these days:
在 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 */
}];
Note: Apparently, this only works on iOS.
注意:显然,这仅适用于 iOS。