在animation边界上更改animationCAShapeLayerpath

我有一个CAShapeLayer(这是一个UIView子类的层),其path应该更新视图的bounds大小更改。 为此,我重写了图层的setBounds:方法来重置path:

 @interface CustomShapeLayer : CAShapeLayer @end @implementation CustomShapeLayer - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; self.path = [[self shapeForBounds:bounds] CGPath]; } 

这工作正常,直到我animation的界限变化。 我想要的pathanimation沿着任何animation边界改变(在UIViewanimation块),以便形状层始终采取其大小。

由于path属性默认没有animation,所以我想出了这个:

覆盖图层的addAnimation:forKey:方法。 在其中,找出边界animation是否被添加到图层。 如果是这样,则通过复制传递给该方法的边界animation中的所有属性,创build第二个显式animation来为边界旁边的path设置animation。 在代码中:

 - (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"]; } } } 

我通过阅读DavidRönnqvist的“ 视图层协同”文章得到了这个想法。 (此代码适用于iOS 8.在iOS 7上,似乎您必须检查animation的keyPath@"bounds"而不是@“bounds.size”。)

触发视图的animation边界更改的调用代码如下所示:

 [UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{ self.customShapeView.bounds = newBounds; } completion:nil]; 

问题

这大部分工作,但我有两个问题:

  1. 偶尔,我在CGPathApply()EXC_BAD_ACCESS )上CGPathApply()这个animation的时候触发了这个animation。 我不确定这是否与我的具体实施有关。 编辑:没关系。 我忘了将UIBezierPath转换为CGPathRef 谢谢@antonioviero !

  2. 当使用标准的UIView弹簧animation时,视图边界和图层path的animation稍微不同步。 也就是说,pathanimation也是以一种有弹性的方式执行的,但它并不完全遵循视图的边界。

  3. 更一般地说,这是最好的方法吗? 似乎有一个形状图层,其path依赖于边界的大小,并且应该与任何边界的变化同步animation,这些东西应该可以工作,但是我很难。 我觉得一定有更好的办法。

其他的事情,我已经尝试

  • 覆盖图层的actionForKey:或视图的actionForLayer:forKey:以便返回path属性的自定义animation对象。 我认为这将是首选的方式,但我没有find一种方法来获得应该由封闭的animation块设置的交易属性。 即使从animation块内部调用, [CATransaction animationDuration ]等也总是返回默认值。

是否有办法(a)确定你当前正在一个animation块中,(b)获取已经在该块中设置的animation属性(持续时间,animation曲线等)?

项目

这是animation:橙色三angular形是形状图层的path。 黑色轮廓是托pipe形状图层的视图的框架。

截图

看看GitHub上的示例项目 。 (这个项目是针对iOS 8的,需要运行Xcode 6,对不起。)

更新

Jose Luis Piedrahita 指出 了Nick Lockwood的这篇文章 ,其中提出了以下方法:

  1. 覆盖视图的actionForLayer:forKey:或图层的actionForKey:方法,并检查传递给此方法的密钥是否是您要设置animation的密钥( @"path" )。

  2. 如果是这样的话,在图层的“常规”animation属性之一(比如@"bounds" )上调用super将隐式地告诉你,如果你在animation块内。 如果视图(图层的委托)返回一个有效的对象(而不是nilNSNull ),我们是。

  3. [super actionForKey:]返回的animation中为pathanimation设置参数(持续时间,计时函数等),并返回pathanimation。

这确实在iOS 7.x下工作得很好。 但是,在iOS 8下,从actionForLayer:forKey:返回的对象不是一个标准(和logging)的CA...Animation而是私有的_UIViewAdditiveAnimationAction类( NSObject子类)的一个实例。 由于这个类的属性没有logging,我不能用它们来轻松创buildpathanimation。

编辑:或者它可能只是工作。 就像Nick在他的文章中提到的那样,像backgroundColor这样的属性仍然会返回一个标准的CA...Animation iOS上的CA...Animation 8.我将不得不尝试一下。

我知道这是一个古老的问题,但我可以为您提供一个类似洛克伍德先生的解决scheme。 可悲的是这里的源代码很快,所以你需要将其转换为ObjC。

如前所述,如果图层是视图的背景层,则可以截取视图本身中的CAAction。 然而,例如如果在多于一个视图中使用背衬层,则这是不方便的。

好消息是actionForLayer:forKey:实际上在后台图层中调用actionForKey:。

它在actionForKey中:在后台层,我们可以拦截这些调用,并为path改变时提供animation。

用swift编写的例子如下:

 class AnimatedBackingLayer: CAShapeLayer { override var bounds: CGRect { didSet { if !CGRectIsEmpty(bounds) { path = UIBezierPath(roundedRect: CGRectInset(bounds, 10, 10), cornerRadius: 5).CGPath } } } override func actionForKey(event: String) -> CAAction? { if event == "path" { if let action = super.actionForKey("backgroundColor") as? CABasicAnimation { let animation = CABasicAnimation(keyPath: event) animation.fromValue = path // Copy values from existing action animation.autoreverses = action.autoreverses animation.beginTime = action.beginTime animation.delegate = action.delegate animation.duration = action.duration animation.fillMode = action.fillMode animation.repeatCount = action.repeatCount animation.repeatDuration = action.repeatDuration animation.speed = action.speed animation.timingFunction = action.timingFunction animation.timeOffset = action.timeOffset return animation } } return super.actionForKey(event) } } 

我觉得你有问题,因为你玩的图层的框架,它是在同一时间的path。

我只需要使用具有自定义drawRect的CustomView:绘制你所需要的,然后就可以了

 [UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{ self.customView.bounds = newBounds; } completion:nil]; 

Sor是根本不需要使用pathes

这是我使用这种方法https://dl.dropboxusercontent.com/u/73912254/triangle.mov

我觉得边界和pathanimation是不同步的,因为UIVIew spring和CABasicAnimation之间的定时函数不同。

也许你可以尝试animation转换,它也应该转换path(未经testing),完成animation后,可以设置边界。

还有一种可能的方式是将path作为快照,将其设置为图层的内容,然后为边界设置animation,然后内容应该跟随animation。

find解决schemeanimationpathCAShapeLayer boundsanimation:

 typedef UIBezierPath *(^PathGeneratorBlock)(); @interface AnimatedPathShapeLayer : CAShapeLayer @property (copy, nonatomic) PathGeneratorBlock pathGenerator; @end @implementation AnimatedPathShapeLayer - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { if ([key rangeOfString:@"bounds.size"].location == 0) { CAShapeLayer *presentationLayer = self.presentationLayer; CABasicAnimation *pathAnim = [anim copy]; pathAnim.keyPath = @"path"; pathAnim.fromValue = (id)[presentationLayer path]; pathAnim.toValue = (id)self.pathGenerator().CGPath; self.path = [presentationLayer path]; [super addAnimation:pathAnim forKey:@"path"]; } [super addAnimation:anim forKey:key]; } - (void)removeAnimationForKey:(NSString *)key { if ([key rangeOfString:@"bounds.size"].location == 0) { [super removeAnimationForKey:@"path"]; } [super removeAnimationForKey:key]; } @end // @interface ShapeLayeredView : UIView @property (strong, nonatomic) AnimatedPathShapeLayer *layer; @end @implementation ShapeLayeredView @dynamic layer; + (Class)layerClass { return [AnimatedPathShapeLayer class]; } - (instancetype)initWithGenerator:(PathGeneratorBlock)pathGenerator { self = [super init]; if (self) { self.layer.pathGenerator = pathGenerator; } return self; } @end