简体   繁体   中英

How to make line track animating CALayer?

I have many animating CALayers, and I want to line some pair of them while animating.

Let's say, there are two animation circle CALayers. I want to make a line tracking the center of CALayers. There are two possible solution came up with me.

1 Whenever what animation add to circle layer, calculate the corresponding animation of the line.

2 Control the whole animation using time calculation, inspired by objc.io . In each step calculate how the circle move from and move to, as well as the line.

There is the problem, in solution one, there will be poor code to control too many circle layers, and for different circle animation, there should be different corresponding. If there are many circles and many kinds of animation, thing will be worse, which is my situation exactly.

In solution two, I must drop Core Animation and do it from scratch, it will increase the complexity and it will be hard to make some adjustment in the future.

Is there a simpler and more elegant way to do this, maybe some tricks behind core animation. Thanks a lot.

UPDATE: According to what @Duncan C point out, I am trying use 3 layer with same time function.

I created the line animation according to the circles. The animation need to be additive, in order to handle multiple animation. But the animation add to the line CAShaperLayer makes the whole view goes black. Anything wrong?

Here is some code. TIA.

- (void)startHorizontalAnimation:(int)duplicateNum {
    // circle animation
    const double distance = 30;
    int totalTime = 600;
    int timeEachDirection = 5;
    double zInterpolation = self.zPosition / duplicateNum;
    double position = distance * 2 * zInterpolation - distance;

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position.x";

    NSMutableArray *valueArr = [[NSMutableArray alloc] initWithObjects:@0, nil];
    for (int i = 0; i < totalTime / (timeEachDirection * 2); ++i) {
        [valueArr addObject:[NSNumber numberWithDouble:position]];
        [valueArr addObject:[NSNumber numberWithDouble:-position]];
    }
    [valueArr addObject:@0];
    animation.values = valueArr;


    animation.additive = YES;
    animation.duration = totalTime;

    NSMutableArray *animationArr = [[NSMutableArray alloc] init];
    for (int i = 0; i < [valueArr count] - 1; ++i) {
        [animationArr addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    }

    animation.timingFunctions = animationArr;

    animation.delegate = self.delegate;

    [self addAnimation:animation forKey:ANIMI_HORIZONTALD_DISPERSE];

    // line animation
    CAAnimation *startAnimation = [self copyPositionFrom:animation modifyStart:YES onDirectionX:YES];
    CAAnimation *endAnimation = [self copyPositionFrom:animation modifyStart:NO onDirectionX:YES];

    [self.lineFrom addAnimation:startAnimation forKey:nil];
    [self.lineTo addAnimation:endAnimation forKey:nil];

}

// create line animation according to the circle animation, changing the start point or end point on direction x or y.
- (CAAnimation *)copyPositionFrom:(CAKeyframeAnimation *)animation modifyStart:(BOOL)modifyStart onDirectionX:(BOOL)directionX {
    CAKeyframeAnimation *newAnimation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
    newAnimation.additive = animation.additive;
    newAnimation.duration = animation.duration;
    newAnimation.timingFunction = animation.timingFunction;
    newAnimation.delegate = animation.delegate;


    CGPathRef lineCGPath = ((CAShapeLayer *)self.lineFrom).path;
    NSMutableArray *bezierPoints = [NSMutableArray array];
    // MyCGPathApplierFunc is according to http://stackoverflow.com/a/5714872/531966
    CGPathApply(lineCGPath, (__bridge void *)(bezierPoints), MyCGPathApplierFunc);

    __block NSMutableArray *pathArray = [NSMutableArray array];
    [animation.values enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger idx, BOOL *stop) {

        CGPoint startPoint = [[bezierPoints objectAtIndex:0] CGPointValue];
        CGPoint endPoint = [[bezierPoints objectAtIndex:1] CGPointValue];

        // modify the position additively
        if (modifyStart) {
            if (directionX) {
                startPoint.x += [value floatValue];
            } else {
                startPoint.y += [value floatValue];
            }
        } else {
            if (directionX) {
                endPoint.x += [value floatValue];
            } else {
                endPoint.y += [value floatValue];
            }
        }

        UIBezierPath *linePath = [UIBezierPath bezierPath];
        [linePath moveToPoint:startPoint];
        [linePath addLineToPoint:endPoint];

        [pathArray addObject:linePath];
    }];

    newAnimation.values = pathArray;
    return newAnimation;
}

@interface CircleShapeLayer : CAShapeLayer

@property (nonatomic, weak) CAShapeLayer *lineFrom; // the line goes from the circle
@property (nonatomic, weak) CAShapeLayer *lineTo; // the line goes to the circle

@end

I would suggest creating your animations in sets, with the same timing.

To animate 2 circles and a line between them, animate each circle layer's position property as a separate CABasicAnimation, and a third CABasicAnimation to animate the line (You'd do this with a CAShapeLayer where you animate the start and endpoints of the path that's in the shape layer.)

Alternately, you could use a single shape layer for both circles and the connecting line and animate all the changes to the path at once (have the path contain 2 circles and the line, and create an animation that changes the control points of 3 at once.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM