如何在整个层次结构中管理CALayer动画

时间:2014-11-13 20:36:00

标签: ios core-animation calayer

这是How to synchronize CALayer and UIView animations up and down a complex hierarchy

的后续问题

假设我有一个复合图层(Top),它是CALayer的子类,并且具有任意数量的子图层。 Top里面有2个子图层。第一个子层(A)应始终为固定宽度 - 比方说宽度为100像素。第二个子层(B)应该是Top的剩余部分。 A和B都应该占据Top的整个高度。在layoutSubviews中进行编码非常简单。

让我们假设Top不知道A或B.还假设Top有一个委托来控制它何时应该被动画化(委托提供actionForLayer:forKey:而没有其他CALayer委托函数)。 / p>

我想制定一个策略,对于每个可能的Top大小,用户将始终看到根据上面列出的约束渲染A和B - 即使Top的大小正在动画,即使它正在使用各种动画参数(持续时间,函数,偏移等)进行动画制作。

正如Top的动画是通过其代表从包含视图或图层的某些动画驱动的 - 似乎A和B应该让他们的动画设置其包含图层 - 顶部。我想把事情做好,所以我不想要A&的布局。 Top中的B需要被除Top之外的任何东西理解。

所以 - 问题是什么是将动画链接到图层树以保持所有动画参数同步的最佳策略?

1 个答案:

答案 0 :(得分:0)

这里有一些示例代码通过使用actionForLayer:forKey:进行链接,但是中间函数必须经过一些相当复杂的工作(不包括在内)才能翻译所有设置它动画到子层的动画。此示例中未包含任何处理插值边界值的代码。例如,假设设置动画以使用不同的fromValue或关键帧动画的情况。这些值需要为子层解决并相应地应用。

#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

我的问题是 - 有没有人有更好的方法来完成这项工作?似乎有点可怕,我没有看到任何提及任何地方的这种分层链接。我知道在取消顶层动画时,我可能还需要做一些关于取消子图层动画的工作。简单地依赖于当前附加的动画,尤其是当前时间关注该功能似乎可能是某个地方的错误来源。

我也不确定这会在野外表现如何,因为它们不在同一个动画组中。任何想法都会非常感激。