如何在整个层次结构中pipe理CALayeranimation
这是如何同步CALayer和UIViewanimation上下复杂层次结构的后续问题
比方说,我有一个复合层(上),是CALayer的子类,并有任何数量的子。 顶部有2个子层。 第一个子层(A)应该总是固定的宽度 – 比如说100像素宽。 第二个子图层(B)应该是Top大小的其余部分。 A和B都应该占据Top的整个高度。 这在layoutSubviews中编码非常简单。
假设Top没有A或B的知识。还假设Top有一个控制何时应该被animation的委托(委托提供actionForLayer:forKey:并且没有其他的CALayer委托函数)。
我想制定一个策略,对于Top的每一个可能的大小,用户将总是看到A和B按照上面列出的约束进行渲染 – 即使当Top的大小被dynamic化时,即使它正在被animation各种animation参数(持续时间,function,偏移量等)。
正如Top的animation是通过其委托来驱动一些包含的视图或图层 – 似乎A和B应该有他们的animation设置其包含的图层 – 顶部。 我想把事情做得很好,所以我不希望top中的A&B的布局需要被Top以外的任何东西所理解。
所以 – 问题是将animation链接到图层树中以保持所有animation参数同步的最佳策略是什么?
下面是一些通过使用actionForLayer:forKey:进行链接的示例代码,但是中间函数必须经过一些相当复杂的工作(不包括在内)才能将其animation的所有设置转换为子图层的animation。 不包括在这个示例中的是处理内插值的任何代码。 例如,假设一个animation被设置为使用不同于fromValue或关键帧animation的情况。 这些值将需要为子层解决并相应地应用。
#import "ViewController.h" @interface MyTopLayer : CALayer @end static const CGFloat fixedWidth = 100.0; @implementation MyTopLayer -(instancetype)init { self = [super init]; if (self) { self.backgroundColor = [[UIColor redColor] CGColor]; CALayer *fixedLayer = [[CALayer alloc] init]; CALayer *slackLayer = [[CALayer alloc] init]; [self addSublayer:fixedLayer]; [self addSublayer:slackLayer]; fixedLayer.anchorPoint = CGPointMake(0,0); fixedLayer.position = CGPointMake(0,0); slackLayer.anchorPoint = CGPointMake(0,0); slackLayer.position = CGPointMake(fixedWidth,0); fixedLayer.backgroundColor = [[UIColor yellowColor] CGColor]; slackLayer.backgroundColor = [[UIColor purpleColor] CGColor]; //fixedLayer.delegate = self; // no reason to ever animate this layer since it is static slackLayer.delegate = self; } return self; } -(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event { if (![event isEqualToString:@"bounds"]) { return nil; } CAAnimation *boundsAnim = [self animationForKey:@"bounds"]; NSLog(@"boundsAnim=%@", boundsAnim); if (!boundsAnim) { return (id<CAAction>)[NSNull null]; } CAAnimation *sublayerBoundsAnim; if ([boundsAnim isKindOfClass:[CABasicAnimation class]]) { CABasicAnimation *subAnim = [CABasicAnimation animationWithKeyPath:@"bounds"]; // transform properties, like from, to & by value from boundsAnim (outer) to the inner layer's animation sublayerBoundsAnim = subAnim; } else { CAKeyframeAnimation *subAnim = [CAKeyframeAnimation animationWithKeyPath:@"bounds"]; // copy/interpolate keyframes sublayerBoundsAnim = subAnim; } sublayerBoundsAnim.timeOffset = boundsAnim.timeOffset; sublayerBoundsAnim.duration = boundsAnim.duration; sublayerBoundsAnim.timingFunction = boundsAnim.timingFunction; return sublayerBoundsAnim; } -(void)layoutSublayers { { CALayer *fixedLayer = [self.sublayers firstObject]; CGRect b = self.bounds; b.size.width = fixedWidth; fixedLayer.bounds = b; } { CALayer *slackLayer = [self.sublayers lastObject]; CGRect b = self.bounds; b.size.width -= fixedWidth; slackLayer.bounds = b; } } @end @interface MyView : UIView @end @implementation MyView { bool _shouldAnimate; } +(Class)layerClass { return [MyTopLayer class]; } -(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.layer.delegate = self; UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapRecognizer:)]; doubleTapRecognizer.numberOfTapsRequired = 2; [self addGestureRecognizer:doubleTapRecognizer]; UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognizer:)]; [tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; [self addGestureRecognizer:tapRecognizer]; } return self; } CGFloat getRandWidth() { const static int maxWidth=1024; const static int minWidth=fixedWidth*1.1; return minWidth+((((CGFloat)rand())/(CGFloat)RAND_MAX)*(maxWidth-minWidth)); } -(void)tapRecognizer:(UITapGestureRecognizer*) gr { _shouldAnimate = true; CGFloat w = getRandWidth(); self.layer.bounds = CGRectMake(0,0,w,self.layer.bounds.size.height); } -(void)doubleTapRecognizer:(UITapGestureRecognizer*) gr { _shouldAnimate = false; CGFloat w = getRandWidth(); self.layer.bounds = CGRectMake(0,0,w,self.layer.bounds.size.height); } -(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event { if (_shouldAnimate) { if ([event isEqualToString:@"bounds"]) { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event]; anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; anim.duration = 2.0; //anim.timeOffset = 0.5; anim.fromValue = [NSValue valueWithCGRect:CGRectMake(0,0,100,100)]; return anim; } else { return nil; } } else { return (id<CAAction>)[NSNull null]; } } @end
我的问题是 – 有没有人有更好的方法来完成这个? 这似乎有点可怕,我还没有看到任何地方提到这种分级链。 我知道,当顶层的animation被取消时,我可能还需要做更多的工作来取消子层animation。 仅仅依靠当前附加的animation,特别是对于当前该函数所处的时间的关注,似乎可能是某处错误的来源。
我也不确定这样做会不会很好,因为他们不在同一个animation组里。 那里的任何想法将不胜感激。