@interface CustomShapeLayer : CAShapeLayer
@end
@implementation CustomShapeLayer
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.path = [[self shapeForBounds:bounds] CGPath];
}
这很好用,直到我动画改变边界。 我希望路径能够在任何动画边界变化(在UIView动画块中)旁边动画,以便形状图层始终适应其大小。
由于path属性默认情况下不会动画,因此我想出了这个方法:
覆盖图层的addAnimation:forKey:方法。 在其中,找出是否正在向图层添加边界动画。 如果是,通过复制传递给该方法的所有属性从而创建第二个显式动画,以便沿着边界动画路径。 代码如下:
- (void)addAnimation:(CAAnimation *)animation forKey:(NSString *)key
{
[super addAnimation:animation forKey:key];
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
if ([basicAnimation.keyPath isEqualToString:@"bounds.size"]) {
CABasicAnimation *pathAnimation = [basicAnimation copy];
pathAnimation.keyPath = @"path";
// The path property has not been updated to the new value yet
pathAnimation.fromValue = (id)self.path;
// Compute the new value for path
pathAnimation.toValue = (id)[[self shapeForBounds:self.bounds] CGPath];
[self addAnimation:pathAnimation forKey:@"path"];
}
}
}
我从阅读David Rönnqvist的View-Layer Synergy article中得到这个想法。(这段代码适用于iOS 8。在iOS 7上,似乎你需要检查动画的
keyPath
与@"bounds"
而不是@"bounds.size"
。)触发视图动画边界变化的调用代码将如下所示:
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{ self.customShapeView.bounds = newBounds;} completion:nil];
问题
这个大部分都能正常工作,但我遇到了两个问题:
偶尔,当触发此动画时前一个动画仍在进行中时,我会在编辑:算了。我忘记将CGPathApply()
中崩溃(EXC_BAD_ACCESS
)。我不确定这是否与我的特定实现有关。UIBezierPath
转换为CGPathRef
。感谢@antonioviero!使用标准的UIView弹簧动画时,视图边界和图层路径的动画略有不同步。也就是说,路径动画也以弹簧方式执行,但它并没有完全跟随视图的边界。
更一般地说,这是最好的方法吗?似乎拥有一个形状图层,其路径依赖于其边界大小,并且应该与任何边界更改同步动画,这似乎是应该可以正常工作的™,但我很难过。我觉得一定有更好的方法。
我尝试过的其他方法
- 覆盖图层的
actionForKey:
或视图的actionForLayer:forKey:
,以返回自定义动画对象来控制path
属性。我认为这是首选的方法,但我没有找到方法来获取由包含动画块设置的事务属性。即使在动画块中调用,[CATransaction animationDuration]
等始终返回默认值。
有没有一种方法可以(a)确定您当前是否在动画块内,并且(b)获取已在该块中设置的动画属性(持续时间,动画曲线等)?
项目
以下是动画内容:橙色三角形是形状图层的路径。黑色轮廓是承载形状图层的视图的框架。
请在GitHub上查看示例项目。(此项目适用于iOS 8,需要Xcode 6才能运行,抱歉。)
更新
Jose Luis Piedrahita 指向了 这篇来自Nick Lockwood的文章,其中建议采取以下方法:
覆盖视图的
actionForLayer:forKey:
或层的actionForKey:
方法,并检查传递给此方法的键是否是您要动画化的键 (@"path"
)。如果是这样,调用层的一个“普通”可动画属性(例如
@"bounds"
)上的super
将隐式地告诉您是否在动画块内。如果视图 (层的委托) 返回一个有效对象 (而不是nil
或NSNull
),则我们处于动画块内。从
[super actionForKey:]
返回的动画中设置路径动画的参数 (持续时间、时间函数等),然后返回路径动画。
在iOS 7.x下,这确实非常有效。然而,在iOS 8下,从actionForLayer:forKey:
返回的对象不是标准(并且已记录)的CA...Animation
,而是私有_UIViewAdditiveAnimationAction
类的实例(一个NSObject
子类)。由于此类的属性未记录,因此我无法轻松地使用它们来创建路径动画。
_编辑:或者它可能仍然有效。正如Nick在他的文章中提到的那样,在iOS 8上,一些属性(如backgroundColor
)仍然返回标准的CA...Animation
。我必须试一试。
path
是一个可动画化的属性。我并不认为这是最好的方法。正如你自己所看到的,它看起来不太牢固。我希望在几分钟内想出一个解决方案。 - duci9yCASpringAnimation
,但它的行为却像一个基本动画。如果你对另一个图层属性(如不透明度或变换)应用相同的技术,它会像预期的那样振荡。 - David Rönnqvist