如何在整个层次结构中管理CALayer动画 [英] How to manage CALayer animations throughout a hierarchy

查看:54
本文介绍了如何在整个层次结构中管理CALayer动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是对如何在复杂的层次结构上下同步CALayer和UIView动画

我说我有一个复合层(顶部),它是CALayer的子类,并具有任意数量的子代。顶部有2个子层。第一子层(A)应始终为固定宽度-假设宽度为100像素。第二个子层(B)应该是Top大小的其余部分。 A和B都应占据Top的整个高度。

Lets say I have a composite layer (Top) that is a subclass of CALayer and has any number of children. Top has 2 child layers within it. The first sublayer (A) should always be a fixed width - lets say 100 pixels wide. The second sublayer (B) should be the remainder of the size of Top. Both A and B should occupy the entire height of Top. This is pretty straightforward to code up in layoutSubviews.

我们假定Top不了解A或B。还假定Top具有一个委托来控制何时应该是A。动画(该委托提供actionForLayer:forKey:,而没有其他CALayer委托函数)。

Let's presume that Top has no knowledge of A or B. Also presume that Top has a delegate that controls when it should be animated (the delegate provides actionForLayer:forKey: and no other CALayer delegate functions).

我想设计一种策略,在该策略中,对于每种可能的Top大小,用户始终会根据上面列出的约束看到A和B渲染-即使对Top的大小进行动画处理,甚至对它使用任何各种动画参数(持续时间,函数,偏移量等)进行动画处理也是如此。

I'd like to devise a strategy where for every possible size of Top, the user will always see A and B rendered according to the constraints listed above - even when the size of Top is being animated, even when it is being animated with any variety of animation parameters (durations, functions, offsets, etc).

就像Top的动画是通过其委托从某个包含视图或图层的驱动一样-似乎A和B应该让其动画设置其包含图层-Top。我想让事情保持井井有条,所以我不希望A&

Just as Top's animations are driven from some containing view or layer through its delegate - it seems that A and B should have their animations setup their containing layer - Top. I want to keep things well-composed, so I don't want the layout of A & B within Top to need to be understood by anything other than Top.

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

So - the question is what's the best strategy to chain the animations down the layer tree to keep all of the animation parameters in sync?

推荐答案

以下是一些示例代码,它们通过使用actionForLayer:forKey:进行链接,但是中间函数必须完成一些相当复杂的工作(不包括在内),以将所有设置从其动画转换为子图层的动画。此样本中未包含任何处理边界值的代码。例如,设想一种情况,将动画设置为使用不同于value的值或关键帧动画。这些值需要为子层求解并相应地应用。

Here's some sample code that does chaining through the use of actionForLayer:forKey:, but middle function has to go through some fairly involved work (which isn't included) to translate all of the settings from its animation to the sublayer's animation. Not included in this sample is any code that deals with interpolating the values of the bounds. For example, imagine a case where an animation is setup to use a different fromValue, or a keyframe animation. Those values would need to be solved for the sublayers and applied accordingly.

#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

我的问题是-是否有人有更好的方法来完成这项工作吗?我似乎从未在任何地方提到这种分层链接,这似乎有点令人恐惧。我知道,取消顶层动画后,我可能还需要做更多的工作来取消子层动画。仅仅依靠当前附加的动画,尤其是在当前没有关注该功能的情况下,似乎很可能是错误的根源。

My question is - does anybody have a better way to get this done? It seems a little bit scary that I've not seen any mention of this sort of hierarchical chaining anywhere. I'm aware that I would probably also need to do some more work on canceling sublayer animations when the top layer's animation is canceled. Relying simply on the currently attached animation, especially w/out concern for the current time that that function is in seems like it could be a source of errors somewhere down the line.

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

I'm also not sure how well this would perform in the wild since they aren't in the same animation group. Any thoughts there would be greatly appreciated.

这篇关于如何在整个层次结构中管理CALayer动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆