在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];
问题
这大部分工作,但我有两个问题:
-
偶尔,我在编辑:没关系。 我忘了将CGPathApply()
(EXC_BAD_ACCESS
)上CGPathApply()
这个animation的时候触发了这个animation。 我不确定这是否与我的具体实施有关。UIBezierPath
转换为CGPathRef
。 谢谢@antonioviero ! -
当使用标准的UIView弹簧animation时,视图边界和图层path的animation稍微不同步。 也就是说,pathanimation也是以一种有弹性的方式执行的,但它并不完全遵循视图的边界。
-
更一般地说,这是最好的方法吗? 似乎有一个形状图层,其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的这篇文章 ,其中提出了以下方法:
-
覆盖视图的
actionForLayer:forKey:
或图层的actionForKey:
方法,并检查传递给此方法的密钥是否是您要设置animation的密钥(@"path"
)。 -
如果是这样的话,在图层的“常规”animation属性之一(比如
@"bounds"
)上调用super
将隐式地告诉你,如果你在animation块内。 如果视图(图层的委托)返回一个有效的对象(而不是nil
或NSNull
),我们是。 -
从
[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解决schemeanimationpath
的CAShapeLayer
bounds
animation:
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