代码之家  ›  专栏  ›  技术社区  ›  Ben Gottlieb

禁用中的隐式动画-[Calayer SetNeedsDisplayInRect:]

  •  128
  • Ben Gottlieb  · 技术社区  · 15 年前

    我有一个图层,在它的-drawInContext:方法中有一些复杂的绘图代码。我正在尝试最小化所需的绘图量,因此我使用-setneedsDisplayInect:仅更新更改的零件。这工作得很好。但是,当图形系统更新我的图层时,它使用交叉淡入淡出从旧图像过渡到新图像。我希望它能立即切换。

    我试过使用cattransaction关闭操作并将持续时间设置为零,但都不起作用。下面是我使用的代码:

    [CATransaction begin];
    [CATransaction setDisableActions: YES];
    [self setNeedsDisplayInRect: rect];
    [CATransaction commit];
    

    在catTransaction上是否有其他方法我应该使用(我还尝试了-setValue:forkey:with kTransactionDisableActions,相同的结果)。

    14 回复  |  直到 6 年前
        1
  •  161
  •   Nikolay Suvandzhiev    6 年前

    您可以通过设置要返回的层上的操作字典来完成此操作。 [NSNull null] 作为适当关键点的动画。例如,我使用

    NSDictionary *newActions = @{
        @"onOrderIn": [NSNull null],
        @"onOrderOut": [NSNull null],
        @"sublayers": [NSNull null],
        @"contents": [NSNull null],
        @"bounds": [NSNull null]
    };
    
    layer.actions = newActions;
    

    在我的某个图层中插入或更改子图层时禁用淡入/淡出动画,以及图层大小和内容的更改。我相信 contents 关键是为了防止更新图形上的交叉淡入淡出而寻找的。


    SWIFT版:

    let newActions = [
            "onOrderIn": NSNull(),
            "onOrderOut": NSNull(),
            "sublayers": NSNull(),
            "contents": NSNull(),
            "bounds": NSNull(),
        ]
    
        2
  •  89
  •   mxcl    14 年前

    也:

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    
    //foo
    
    [CATransaction commit];
    
        3
  •  28
  •   user3378170    9 年前

    当更改层的属性时,CA通常创建一个隐式事务对象来动画更改。如果不希望对更改进行动画处理,可以通过创建显式事务并设置其 KCA事务禁用 属性到 .

    Objtovi-C

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    // change properties here without animation
    [CATransaction commit];
    

    迅捷

    CATransaction.begin()
    CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
    // change properties here without animation
    CATransaction.commit()
    
        4
  •  23
  •   Community CDub    7 年前

    除了 Brad Larson's answer :对于自定义层(由您创建) you can use delegation 而不是修改层 actions 字典。这种方法更具动态性,可能更具性能。它允许禁用所有隐式动画,而不必列出所有可设置动画的关键点。

    不幸的是,它不可能使用 UIView 作为自定义层代理,因为 UIVIEW 已经是其自身层的委托。但是您可以使用这样一个简单的助手类:

    @interface MyLayerDelegate : NSObject
        @property (nonatomic, assign) BOOL disableImplicitAnimations;
    @end
    
    @implementation MyLayerDelegate
    
    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
    {
        if (self.disableImplicitAnimations)
             return (id)[NSNull null]; // disable all implicit animations
        else return nil; // allow implicit animations
    
        // you can also test specific key names; for example, to disable bounds animation:
        // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
    }
    
    @end
    

    用法(在视图内):

    MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];
    
    // assign to a strong property, because CALayer's "delegate" property is weak
    self.myLayerDelegate = delegate;
    
    self.myLayer = [CALayer layer];
    self.myLayer.delegate = delegate;
    
    // ...
    
    self.myLayerDelegate.disableImplicitAnimations = YES;
    self.myLayer.position = (CGPoint){.x = 10, .y = 42}; // will not animate
    
    // ...
    
    self.myLayerDelegate.disableImplicitAnimations = NO;
    self.myLayer.position = (CGPoint){.x = 0, .y = 0}; // will animate
    

    有时,将视图的控制器作为视图自定义子层的委托是很方便的;在这种情况下,不需要助手类,您可以实现 actionForLayer:forKey: 方法就在控制器内部。

    重要提示:不要尝试修改的委托 UIVIEW 的底层(例如启用隐式动画)会发生不好的事情。

    注意:如果要动画(而不是禁用动画)层重绘,则放置 [CALayer setNeedsDisplayInRect:] 内部调用 CATransaction 因为实际的重新绘制有时会(也可能会)发生。好的方法是使用自定义属性,如前所述 in this answer .

        5
  •  8
  •   Sam Soffes Jolly Roger    8 年前

    这里有一个更有效的解决方案,类似于公认的答案,但是 迅捷 . 在某些情况下,它会比每次修改值时创建事务要好,这是一个性能问题,正如其他人所提到的,例如在60fps左右拖动层位置的常见用例。

    // Disable implicit position animation.
    layer.actions = ["position": NSNull()]      
    

    查看苹果的文档 how layer actions are resolved . 实现委托将跳过级联中的另一个级别,但在我的示例中,由于 caveat about the delegate needing to be set to the associated UIView .

    编辑:更新感谢评论指出 NSNull 符合 CAAction .

        6
  •  7
  •   bob    11 年前

    根据山姆的回答和西蒙的困难…创建csshapelayer后添加委托引用:

    CAShapeLayer *myLayer = [CAShapeLayer layer];
    myLayer.delegate = self; // <- set delegate here, it's magic.
    

    …在“M”文件的其他地方…

    基本上与Sam的相同,没有通过自定义“DisableImplicitations”变量排列切换的能力。更多的是“硬线”方法。

    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    
        // disable all implicit animations
        return (id)[NSNull null];
    
        // allow implicit animations
        // return nil;
    
        // you can also test specific key names; for example, to disable bounds animation:
        // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
    
    }
    
        7
  •  7
  •   MJN    10 年前

    实际上,我没有找到任何正确的答案。为我解决问题的方法是:

    - (id<CAAction>)actionForKey:(NSString *)event {   
        return nil;   
    }
    

    然后你可以在它的任何逻辑,禁用一个特定的动画,但因为我想删除它们全部,我返回了零。

        8
  •  5
  •   rounak    6 年前

    找到了一个更简单的方法来禁用 CATransaction 内部呼叫 setValue:forKey: 对于 kCATransactionDisableActions 密钥:

    [CATransaction setDisableActions:YES];
    

    Swift:

    CATransaction.setDisableActions(true)
    
        9
  •  3
  •   Chris Long user9414135    7 年前

    禁用Swift中的隐式层动画

    CATransaction.setDisableActions(true)
    
        10
  •  2
  •   Kamran Khan    10 年前

    将它添加到正在实现-drawrect()方法的自定义类中。对代码进行更改以满足您的需求,对我来说,“不透明度”是停止交叉淡入淡出动画的诀窍。

    -(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
    {
        NSLog(@"key: %@", key);
        if([key isEqualToString:@"opacity"])
        {
            return (id<CAAction>)[NSNull null];
        }
    
        return [super actionForLayer:layer forKey:key];
    }
    
        11
  •  0
  •   Warpling    9 年前

    截至 IOS 7 有一种方便的方法可以做到这一点:

    [UIView performWithoutAnimation:^{
        // apply changes
    }];
    
        12
  •  0
  •   Community CDub    7 年前

    要在更改catextlayer的字符串属性时禁用恼人(模糊)动画,可以执行以下操作:

    class CANullAction: CAAction {
        private static let CA_ANIMATION_CONTENTS = "contents"
    
        @objc
        func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
            // Do nothing.
        }
    }
    

    然后像这样使用它(不要忘记正确设置你的catextlayer,例如正确的字体等):

    caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
    

    您可以在这里看到我对catextlayer的完整设置:

    private let systemFont16 = UIFont.systemFontOfSize(16.0)
    
    caTextLayer = CATextLayer()
    caTextLayer.foregroundColor = UIColor.blackColor().CGColor
    caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
    caTextLayer.fontSize = systemFont16.pointSize
    caTextLayer.alignmentMode = kCAAlignmentCenter
    caTextLayer.drawsAsynchronously = false
    caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
    caTextLayer.contentsScale = UIScreen.mainScreen().scale
    caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)
    
    uiImageTarget.layer.addSublayer(caTextLayer)
    caTextLayer.string = "The text you want to display"
    

    现在,您可以根据需要更新catextlayer.string=)

    受到启发 this this 回答。

        13
  •  0
  •   gm333    9 年前

    试试这个。

    let layer = CALayer()
    layer.delegate = hoo // Same lifecycle UIView instance.
    

    警告

    如果设置uiTableView实例的委托,有时会发生崩溃(可能是ScrollView的HitTest递归调用)。

        14
  •  0
  •   Martin CR    8 年前

    如果你需要一个非常快速(但必须承认是黑客)的修复,那么它可能值得你做(迅速):

    let layer = CALayer()
    
    // set other properties
    // ...
    
    layer.speed = 999