可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Which is the most elegant and modular way to chain animation in a Core Animation context?
I mean to do animations that starts just when other finished (for example, changing position
and then opacity
).. normal approach is to directly change properties:
layer.position = new_point;
layer.opacity = 0.0f;
but this will do them at the same time. I want to make one wait for the other.
And what about chaining animations for different objects? I've read about CATransaction
used like:
[CATransaction begin]
layer1.property = new_property;
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
[CATransaction commit];
but it doesn't seem to work..
回答1:
You can also use animation grouping and use the beginTime field of the animation. Try something like this:
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
[posAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[posAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here's the important part
[posAnimation setDuration:10.0];
[posAnimation setBeginTime:0.0];
CABasicAnimation *borderWidthAnimation = [CABasicAnimation animationWithKeyPath:@"borderWidth"];
[borderWidthAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[borderWidthAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here's the important part
[borderWidthAnimation setDuration:10.0];
[borderWidthAnimation setBeginTime:5.0];
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:10.0];
[group setAnimations:[NSArray arrayWithObjects:posAnimation, borderWidthAnimation, nil]];
[layer addAnimation:group forKey:nil];
Notice that the duration of the entire animation is 10 seconds. The first one starts at second 0 and the second one starts at 5 seconds.
回答2:
As Matt pointed out, you can create animation groups that consist of different animations for the same layer with different begin times. You can also set a delegate for stand-alone CAAnimation
objects or CAAnimation
Groups and as each animation finishes it will call an animationDidStop:finished:
delegate method (note that animations that are part of a group won't call their delegate's animationDidStop:finished:
method.
I figured out a cool trick that makes using the CAAnimation animationDidStop:finished:
method more powerful. I use the method setValue:forKey:
to add a block of code to a stand-alone animation or animation group, with the key @"animationCompletionBlock". I then write a general animationDidStop:finished:
method that checks the just-completed animation for a @"animationCompletionBlock" key, and if it finds it, execute the block of code there.
Take a look at this project on github for a working example of that technique:
CAAnimation demo with completion blocks
You an also Set a group of animations inside a
[CATransaction begin];
//...
[[CATransaction commit];
block, as you suggested. When you do that, you can using the CATransaction
class method setCompletionBlock:
to invoke a block of code when all the animations in the current transaction group complete. The completion block for one transaction can then trigger the next transaction.
回答3:
I pull this off using the setCompletionBlock
method to define a closure that triggers the next animation when the first one is finished:
[CATransaction begin]
layer1.property = new_property;
CATransaction.setCompletionBlock {
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
}
[CATransaction commit];
回答4:
I don't believe you can "nest" CA animations as you have in your example.
You need to specify a delegate for the animation and put your second "transition" within the animationDidStop:finished:
selector of the delegate.
Might want to have a look at Apple's Animation Types & Timing Programming Guide.
回答5:
What I have always preferred to setting the begin and end time of each animation seperately is this:
I used A2DynamicDelegate (whose development is now happening in the BlocksKit-Repo, who knows why <_<) to implement a completionBlock property in a Category on CAAnimation.
This enabled me to do stuff like this:
CAAnimation *a = ...
CAAnimation *b = ...
CAAnimation *c = ...
a.completionHandler = ^{
[self.layer addAnimation:b forKey:@"foo"];
[self.layer addAnimation:c forKey:@"bar"];
};
Much more flexible :)
I've uploaded my code for completion handler here. Have a look at the notice in the header file though. I am really confused why the method isn't called.
回答6:
Without including all the "tricks" up my "toolchain", this example isn't directly copy/pastable… but it does show a REALLY easy strategy for "chained" animations..
CATransform3D trans = m34(); // define chain-wide constants.
// Name your "stack". My "nextObject" returns arr[i]->done == nil.
NSArray *layerStack = layer.sublayers;
//define a block, that "takes" a layer as it's argument.
void(^__block ChainBlock)(CALayer*) = ^(CALayer *m) {
// animations, transforms, etc for each inividual "step".
[m animate:@"transform"
// These are just NSValue-wrapped CAT3D's
from:AZV3d(CATransform3DRotate(trans, 0,1,0,0))
to:AZV3d(CATransform3DRotate(trans,1.5,1,0,0))
time:2 // total time == each step * layerStack.count
eased:kCAMediaTimingFunctionEaseOut
completion:^{ // In completion, look for "next" layer.
CAL* m2 = [layers nextObject];
// If there is "another" layer, call this block, again... with it.
if (m2) chainAnis(m2);
// Otherise,you're done. Cleanup, toggle values, whatevs.
else self.finishedProperty = YES;
}];
};
// Give the block we just defined your "first" layer.
ChainBlock(layerStack[0]); // It will recursively feed itself.
This obviously depends on some "external magic", but the concept is simple, and eliminates (through dependencies) the need to "deal with" ANY sort of gross delegation. In particular, the animate:from:to:time:easing:completion
, etc. categories come from the great FunSize Framework, on Github.