Sequence animation using CAAnimationGroup

2020-02-01 01:36发布

问题:

I'm trying to make a sequence of animations, I've found in CAAnimationGroup the right class to achieve that object. In practice I'm adding on a view different subviews and I'd like to animate their entry with a bounce effect, the fact is that I want to see their animations happening right after the previous has finished. I know that I can set the delegate, but I thought that the CAAnimationGroup was the right choice.
Later I discovered that the group animation can belong only to one layer, but I need it on different layers on screen. Of course on the hosting layer doesn't work. Some suggestions?

- (void) didMoveToSuperview {
    [super didMoveToSuperview];
    float startTime = 0;
    NSMutableArray * animArray = @[].mutableCopy;
    for (int i = 1; i<=_score; i++) {
        NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: self.greenLeaf];
        UIImageView * greenLeafImageView = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
        greenLeafImageView.image = [UIImage imageNamed:@"greenLeaf"];
        CGPoint leafCenter =  calculatePointCoordinateWithRadiusAndRotation(63, -(M_PI/11 * i) - M_PI_2);
        greenLeafImageView.center = CGPointApplyAffineTransform(leafCenter, CGAffineTransformMakeTranslation(self.bounds.size.width/2, self.bounds.size.height));
        [self addSubview:greenLeafImageView];

        //Animation creation
        CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
        greenLeafImageView.layer.transform = CATransform3DIdentity;
        bounceAnimation.values = @[
                                  [NSNumber numberWithFloat:0.5],
                                  [NSNumber numberWithFloat:1.1],
                                  [NSNumber numberWithFloat:0.8],
                                  [NSNumber numberWithFloat:1.0]
                                  ];

        bounceAnimation.duration = 2;
        bounceAnimation.beginTime = startTime;
        startTime += bounceAnimation.duration;

        [animArray addObject:bounceAnimation];
        //[greenLeafImageView.layer addAnimation:bounceAnimation forKey:nil];


    }
    // Rotation animation
    [UIView animateWithDuration:1 animations:^{
        self.arrow.transform = CGAffineTransformMakeRotation(M_PI/11 * _score);
    }];
    CAAnimationGroup * group = [CAAnimationGroup animation];
    group.animations = animArray;
    group.duration = [[ animArray valueForKeyPath:@"@sum.duration"] floatValue];
    [self.layer addAnimation:group forKey:nil];
}

回答1:

CAAnimationGroup is meant for having multiple CAAnimation subclasses being stacked together to form an animation, for instance, one animation can perform an scale, the other moves it around, while a third one can rotate it, it's not meant for managing multiple layers, but for having multiple overlaying animations.

That said, I think the easiest way to solve your issue, is to assign each CAAnimation a beginTime equivalent to the sum of the durations of all the previous ones, to illustrate:

for i in 0 ..< 20
{
    let view : UIView = // Obtain/create the view...;
    let bounce = CAKeyframeAnimation(keyPath: "transform.scale")

    bounce.duration  = 0.5;
    bounce.beginTime = CACurrentMediaTime() + bounce.duration * CFTimeInterval(i);

    // ...

    view.layer.addAnimation(bounce, forKey:"anim.bounce")
}

Notice that everyone gets duration * i, and the CACurrentMediaTime() is a necessity when using the beginTime property (it's basically a high-precision timestamp for "now", used in animations). The whole line could be interpreted as now + duration * i.

Must be noted, that if a CAAnimations is added to a CAAnimationGroup, then its beginTime becomes relative to the group's begin time, so a value of 5.0 on an animation, would be 5.0 seconds after the whole group starts. In this case, you don't use the CACurrentMediaTime()



回答2:

If you review the documentation, you will note that CAAnimationGroup inherits from CAAnimation, and that CAAnimation can only be assigned to one CALayer. It's intent is really to make it easy to create and manage multiple animations you wish to apply to a CALayer at the same time, not to manager animations for multiple CALayer objects.

To handle the sequencing of different animations between different CALayer or UIViewobjects, a technique I use is to create an NSOperation for each object/animation, then throw them into a NSOperationQueue to manage the sequencing. This is a bit complicated as you have to use the animation completion callback to tell the NSOperation it is finished, but if you write a good animation management subclass of NSOperation, it can be rather convenient and allow you to create sophisticated sequencing paths. The low-rent way of accomplishing the sequencing goal is to simply set the beginTime property on your CAAnimation object (which comes from it's adoption of the CAMediaTiming protocol) as appropriate to get the timing you want.

With that said, I am going to point you to some code that I wrote and open-sourced to solve the exact same use case you describe. You may find it on github here (same code included). I will add the following notes:

  • My animation management code allow your to define your animation in a plist by identifying the sequence and timing of image changes, scale changes, position changes, etc. It's actually pretty convenient and cleaner to adjust your animation in a plist file rather than in code (which is why I wrote this).
  • If the user is not expected to interact with the subviews you creating, it's actually much better (less overhead) to create layer objects that are added as sub-layers to your hosting view's layer.