使用actionForKey的自定义属性动画:如何获取属性的新值?

时间:2012-04-24 13:29:59

标签: core-animation

在我正在研究的CALayer子类中,我有一个自定义属性,我想自动设置动画,也就是说,假设该属性被称为“myProperty”,我想要以下代码:

[myLayer setMyProperty:newValue];

使动画从当前值平滑到“newValue”。

使用覆盖actionForKey:和needsDisplayForKey的方法:(参见下面的代码)我能够很好地运行它来简单地在旧值和新值之间进行插值。

我的问题是我想使用稍微不同的动画持续时间或路径(或其他),具体取决于属性的当前值和新值,我无法弄清楚如何获取新值来自actionForKey:

提前致谢

@interface ERAnimatablePropertyLayer : CALayer {
    float myProperty;
}
@property (nonatomic, assign) float myProperty;
@end
@implementation ERAnimatablePropertyLayer
@dynamic myProperty;

- (void)drawInContext:(CGContextRef)ctx {
     ... some custom drawing code based on "myProperty"
}

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([key isEqualToString:@"myProperty"]) {
        CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
        theAnimation.fromValue = [[self presentationLayer] valueForKey:key];

        ... I want to do something special here, depending on both from and to values...


        return theAnimation;
        }
    return [super actionForKey:key];
    }

+ (BOOL)needsDisplayForKey:(NSString *)key {
    if ([key isEqualToString:@"myProperty"])
        return YES;
    return [super needsDisplayForKey:key];
    }
    @end

3 个答案:

答案 0 :(得分:2)

您需要避免为要设置动画的属性设置自定义getter和setter。

覆盖didChangeValueForKey:方法。用它来设置要设置动画的属性的模型值。

不要在动作动画上设置toValue。

@interface MyLayer: CALayer

    @property ( nonatomic ) NSUInteger state;

@end

-

@implementation MyLayer

@dynamic state;

- (id<CAAction>)actionForKey: (NSString *)key {

    if( [key isEqualToString: @"state"] )
    {
        CABasicAnimation * bgAnimation = [CABasicAnimation animationWithKeyPath: @"backgroundColor"];
        bgAnimation.fromValue = [self.presentationLayer backgroundColor];
        bgAnimation.duration  = 0.4;

        return bgAnimation;
    }

    return [super actionForKey: key];
}

- (void)didChangeValueForKey: (NSString *)key {

    if( [key isEqualToString: @"state"] )
    {
        const NSUInteger state = [self valueForKey: key];
        UIColor * newBackgroundColor;

        switch (state) 
        {
            case 0: 
                newBackgroundColor = [UIColor redColor]; 
                break;

            case 1: 
                newBackgroundColor = [UIColor blueColor]; 
                break;

            case 2: 
                newBackgroundColor = [UIColor greenColor]; 
                break;

            default: 
                newBackgroundColor = [UIColor purpleColor]; 
        }

        self.backgroundColor = newBackgroundColor.CGColor;
    }

    [super didChangeValueForKey: key];
}

@end

答案 1 :(得分:2)

核心动画在更新属性值之前调用actionForKey:。它通过发送runActionForKey:object:arguments:更新属性值后运行操作。 CAAnimation的{​​{1}}实施仅调用runActionForKey:object:arguments:

您可以返回[object addAnimation:self forKey:key],而不是从actionForKey:返回动画,该CAAction在运行时会创建并安装动画。像这样:

@interface MyAction: NSObject <CAAction>
@property (nonatomic, strong) id priorValue;
@end

@implementation MyAction

- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict {
    ERAnimatablePropertyLayer *layer = anObject;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
    id newValue = [layer valueForKey:key];
    // Set up animation using self.priorValue and newValue to determine
    // fromValue and toValue. You could use a CAKeyframeAnimation instead of
    // a CABasicAnimation.
    [layer addAnimation:animation forKey:key];
}

@end

@implementation ERAnimatablePropertyLayer

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([key isEqualToString:@"myProperty"]) {
        MyAction *action = [[MyAction alloc] init];
        action.priorValue = [self valueForKey:key];
        return action;
    }
    return [super actionForKey:key];
}

你可以找到一个类似技术的工作示例(在Swift中,动画cornerRadiusin this answer

答案 2 :(得分:1)

您可以在CATransaction中存储旧值和新值。

-(void)setMyProperty:(float)value
{
    NSNumber *fromValue = [NSNumber numberWithFloat:myProperty];
    [CATransaction setValue:fromValue forKey:@"myPropertyFromValue"];

    myProperty = value;

    NSNumber *toValue = [NSNumber numberWithFloat:myProperty];
    [CATransaction setValue:toValue forKey:@"myPropertyToValue"];
}

- (id <CAAction>)actionForKey:(NSString *)key {
    if ([key isEqualToString:@"myProperty"]) {
        CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
        theAnimation.fromValue = [[self presentationLayer] valueForKey:key];
        theAnimation.toValue = [CATransaction objectForKey:@"myPropertyToValue"];

        // here you do something special.
    }

    return [super actionForKey:key];
}