在我正在研究的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
答案 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中,动画cornerRadius
)in 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];
}