【问题标题】:Disabling NSView fade animation for NSView `setHidden:`为 NSView `setHidden:` 禁用 NSView 淡入淡出动画
【发布时间】:2013-11-12 08:08:45
【问题描述】:

我正在开发一个具有可拖动控件概念的项目,一切正常,除了 NSView 在调用setHidden: 时似乎采用了淡入/淡出动画。

我已经能够通过将session.animatesToStartingPositionsOnCancelOrFail = YES; 行更改为 NO 并使用自定义动画NSWindow 子类自己实现图像快照来解决该问题。看起来不错,但我知道一定有更简单的方法。

我试过了:

  1. setHidden: 调用周围使用持续时间为 0 的 NSAnimationContext 分组
  2. 使用控件和超级视图上的各种键(alpha、hidden、isHidden)设置视图动画字典
  3. 为控件及其父视图覆盖 animationForKey:

我没有使用 CALayers,甚至尝试将 wantsLayer: 显式设置为 NO。

有人知道如何禁用此动画,或者有比我的动画NSWindow 更简单的解决方案吗?

这里是我精简的修改代码,以了解我在说什么。

@implementation NSControl (DragControl)

- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
    NSImage* image = [self imageForCell:cell];
    NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
    NSRect dragFrame = [self frameForCell:cell];
    dragFrame.size = image.size;
    [di setDraggingFrame:dragFrame contents:image];

    NSArray* items = [NSArray arrayWithObject:di];

    [self setHidden:YES];
    return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}

- (NSRect)frameForCell:(NSCell*)cell
{
    // override in multi-cell cubclasses!
    return self.bounds;
}

- (NSImage*)imageForCell:(NSCell*)cell
{
    return [self imageForCell:cell highlighted:[cell isHighlighted]];
}

- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
    // override in multicell cubclasses to just get an image of the dragged cell.
    // for any single cell control we can just make sure that cell is the controls cell

    if (cell == self.cell || cell == nil) { // nil signifies entire control
                                            // basically a bitmap of the control
                                            // NOTE: the cell is irrelevant when dealing with a single cell control
        BOOL isHighlighted = [cell isHighlighted];
        [cell setHighlighted:highlight];

        NSRect cellFrame = [self frameForCell:cell];

        // We COULD just draw the cell, to an NSImage, but button cells draw their content
        // in a special way that would complicate that implementation (ex text alignment).
        // subclasses that have multiple cells may wish to override this to only draw the cell
        NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
        NSImage* image = [[NSImage alloc] initWithSize:rep.size];

        [self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
        [image addRepresentation:rep];
        // reset the original cell state
        [cell setHighlighted:isHighlighted];
        return image;
    }
    // cell doesnt belong to this control!
    return nil;
}

#pragma mark NSDraggingDestination

- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
    [self setHidden:NO];
}

@end

@implementation NSActionCell (DragCell)

- (void)setControlView:(NSView *)view
{
    // this is a bit of a hack, but the easiest way to make the control dragging work.
    // force the control to accept image drags.
    // the control will forward us the drag destination events via our DragControl category

    [view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
    [super setControlView:view];
}

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
    BOOL result = NO;
    NSPoint currentPoint = theEvent.locationInWindow;
    BOOL done = NO;
    BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];

    BOOL mouseIsUp = NO;
    NSEvent *event = nil;
    while (!done)
    {
        NSPoint lastPoint = currentPoint;

        event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
                                   untilDate:[NSDate distantFuture]
                                      inMode:NSEventTrackingRunLoopMode
                                     dequeue:YES];

        if (event)
        {
            currentPoint = event.locationInWindow;

            // Send continueTracking.../stopTracking...
            if (trackContinously)
            {
                if (![self continueTracking:lastPoint
                                         at:currentPoint
                                     inView:controlView])
                {
                    done = YES;
                    [self stopTracking:lastPoint
                                    at:currentPoint
                                inView:controlView
                             mouseIsUp:mouseIsUp];
                }
                if (self.isContinuous)
                {
                    [NSApp sendAction:self.action
                                   to:self.target
                                 from:controlView];
                }
            }

            mouseIsUp = (event.type == NSLeftMouseUp);
            done = done || mouseIsUp;

            if (untilMouseUp)
            {
                result = mouseIsUp;
            } else {
                // Check if the mouse left our cell rect
                result = NSPointInRect([controlView
                                        convertPoint:currentPoint
                                        fromView:nil], cellFrame);
                if (!result)
                    done = YES;
            }

            if (done && result && ![self isContinuous])
                [NSApp sendAction:self.action
                               to:self.target
                             from:controlView];
            else {
                done = YES;
                result = YES;

                // this bit-o-magic executes on either a drag event or immidiately following timer expiration
                // this initiates the control drag event using NSDragging protocols
                NSControl* cv = (NSControl*)self.controlView;
                NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
                                                                                event:theEvent];
                // Note that you will get an ugly flash effect when the image returns if this is set to yes
                // you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
                // create the window in the drag ended method for NSDragOperationNone
                // there is [probably a better and easier way around this behavior by playing with view animation properties.
                session.animatesToStartingPositionsOnCancelOrFail = YES;
            }

        }
    }
    return result;
}

#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
    switch(context) {
        case NSDraggingContextOutsideApplication:
            return NSDragOperationNone;
            break;

        case NSDraggingContextWithinApplication:
        default:
            return NSDragOperationPrivate;
            break;
    }
}

- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
    // now tell the control view the drag ended so it can do any cleanup it needs
    // this is somewhat hackish
    [self.controlView draggingEnded:nil];
}

@end

【问题讨论】:

    标签: objective-c cocoa core-animation nsview


    【解决方案1】:

    必须在视图层次结构中的某处启用图层,否则不会有淡入淡出动画。这是我禁用此类动画的方法:

    @interface NoAnimationImageView : NSImageView
    
    @end
    
    @implementation NoAnimationImageView
    
    + (id)defaultAnimationForKey: (NSString *)key
    {
        return nil;
    }
    
    @end
    

    【讨论】:

    • 谢谢,但这不起作用。我已经尝试在 NSButton 类别以及 NSView 本身的类别中覆盖 animationForKey:defaultAnimationForKey:。您是否使用我的示例代码对此进行了测试?
    • 无法覆盖类别中的方法,您需要子类化。
    • @mahaltertin 可以在类别中覆盖。我已经做到了(无论如何我尝试了子类,但并没有更好)。
    【解决方案2】:

    您已经通过设置视图动画字典尝试的解决方案应该可以工作。但不是针对您提到的键,而是针对以下键。在第一次触发动画之前的某个地方使用它。如果您必须在窗口或视图或两者上进行,我不知道。

    NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
    [animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderIn];
    [animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderOut];
    [[theViewOrTheWindow animator] setAnimations:animations];
    

    或者如果它们存在也只是删除键(可能不是这种情况,因为它们是隐式/默认的):

    NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
    [animations removeObjectForKey:NSAnimationTriggerOrderIn];
    [animations removeObjectForKey:NSAnimationTriggerOrderOut];
    [[theViewOrTheWindow animator] setAnimations:animations];
    

    【讨论】:

      【解决方案3】:

      好的。我发现我看到的动画不是控件、超级视图或控件的窗口。似乎animatesToStartingPositionsOnCancelOrFail 导致NSDraggingSession 创建一个窗口(使用 QuartzDebug 观察)并将拖动图像放入其中,正是这个窗口动画回到原点并淡出 before @ 987654323@调用被执行(即在拖拽操作结束之前)。

      很遗憾,它创建的窗口不是NSWindow,因此在NSWindow 上创建类别不会禁用淡入淡出动画。

      其次,我知道没有公开的方式来获取窗口的句柄,因此我无法尝试直接操作窗口实例。

      看起来我的解决方法可能是最好的方法,毕竟它与 AppKit 为你做的不远。

      如果有人知道如何处理这个窗口,或者我有兴趣知道它是什么类。

      【讨论】:

        猜你喜欢
        • 2014-10-16
        • 2017-03-13
        • 2012-06-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-20
        • 2012-12-18
        • 2019-02-11
        相关资源
        最近更新 更多