Synchronising image, text, and positioning with CoreAnimation

≡放荡痞女 提交于 2019-12-08 07:17:06

问题


I am a bit of a beginner with animations and have been experimenting with CoreAnimation for a couple of days. Feel free to warn me if this question does not make sense, but I'm trying to achieve the following. I have three objects:

  • one should be an image, moving according to a given pattern
  • one should be an UIImage that swaps two images
  • one should be a text (CATextLayer?) whose content changes

The three actions should happen in sync.

As an example, think about a programme showing a sinusoid function, like in a ECG, oscillating between -1 and +1: the first image would then move according to the current value (-1, -0.9, -0.8, ... 0, +0.1, +0.2, ... 1), the swap image would show "+" for positive values and "-" for negative values, and the text would alternate between "Positive" and "Negative".

I've tried with CAAnimationGroup but I'm clearly missing something. Some code:

{
    // image that moves
    CALayer *img1 = [CALayer layer];
    img1.bounds = CGRectMake(0, 0, 20, 20);
    img1.position = CGPointMake(x1,y1);
    UIImage *img1Image = [UIImage imageNamed:@"image.png"];
    img1.contents = (id)img1Image.CGImage;

    // image that changes 
    CALayer *swap = [CALayer layer];
    swap.bounds = CGRectMake(0, 0, 30, 30);
    swap.position = CGPointMake(x2,y2);
    NSString* nameswap = @"img_swap_1.png"
    UIImage *swapImg = [UIImage imageNamed:nameswap];

    // text
    CATextLayer *text = [CATextLayer layer];
    text.bounds = CGRectMake(0, 0, 100, 100);
    text.position = CGPointMake(x3,y3);
    text.string = @"Text";


    // create animations
    CGFloat duration = 0.2;
    CGFloat totalDuration = 0.0;
    CGFloat start = 0;

    NSMutableArray* animarray = [[NSMutableArray alloc] init];
    NSMutableArray* swapanimarray = [[NSMutableArray alloc] init];
    NSMutableArray* textanimarray = [[NSMutableArray alloc] init];

    float prev_x = 0;
    float prev_y = 0;

    // I get my values for moving the object
    for (NSDictionary* event in self.events) {

            float actual_x = [[event valueForKey:@"x"] floatValue];
            float actual_y = [[event valueForKey:@"y"] floatValue];


            // image move animation
            CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];            
            CGPoint startPt = CGPointMake(prev_x,prev_y);
            CGPoint endPt = CGPointMake(actual_x, actual_y);
            anim.duration = duration;
            anim.fromValue = [NSValue valueWithCGPoint:startPt];
            anim.toValue = [NSValue valueWithCGPoint:endPt];
            anim.beginTime = start;
            anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];            
            [animarray addObject:anim];


            // image swap animation
                        CABasicAnimation *swapanim = [CABasicAnimation animationWithKeyPath:@"contents"];

            swapanim.duration = duration;
            swapanim.beginTime = start;

            NSString* swapnamefrom = [NSString stringWithFormat:@"%@.png", prev_name];
            NSString* swapnameto = [NSString stringWithFormat:@"%@.png", current_name];
            UIImage *swapFromImage = [UIImage imageNamed:swapnamefrom];
            UIImage *swapToImage = [UIImage imageNamed:swapnameto];
            swapanim.fromValue = (id)(swapFromImage.CGImage);
            swapanim.toValue = (id)(swapToImage.CGImage);
            swapanim.fillMode = kCAFillModeForwards;
            swapanim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];      
            [swapanimarray addObject:swapanim];

            //text animation
            CABasicAnimation *textanim = [CABasicAnimation animationWithKeyPath:@"contents"];            
            textanim.duration = duration;
            textanim.fromValue = @"Hey";
            textanim.toValue = @"Hello";
            textanim.beginTime = start;
            [textanimarray addObject:textanim];


            // final time settings
            prev_x = actual_x;
            prev_y = actual_y;
            start = start + duration;
            totalDuration = start + duration;

        }

    }


    CAAnimationGroup* group = [CAAnimationGroup animation];
    [group setDuration:totalDuration]; 
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    [group setAnimations:animarray];

    CAAnimationGroup* swapgroup = [CAAnimationGroup animation];
    [swapgroup setDuration:totalDuration]; 
    swapgroup.removedOnCompletion = NO;
    swapgroup.fillMode = kCAFillModeForwards;
    [swapgroup setAnimations:swapanimarray];

    CAAnimationGroup* textgroup = [CAAnimationGroup animation];
    [textgroup setDuration:totalDuration]; 
    textgroup.removedOnCompletion = NO;
    textgroup.fillMode = kCAFillModeForwards;
    [textgroup setAnimations:textanimarray];

    [ball addAnimation:group forKey:@"position"];
    [swap addAnimation:flaggroup forKey:@"position"];
    [text addAnimation:textgroup forKey:@"contents"];

    [self.layer addSublayer:ball];
    [self.layer addSublayer:swap];
    [self.layer addSublayer:text];


}

Now... the problems:

1) the swap image reverts to the original at every swap. So, if I swap A->B, I see it going from A to B in the expected duration time, but then reverting to A. I've read a number of threads on SO about this but couldn't get it to work.

2) changing the string of the text layer in a timed fashion... is this possible with this infrastructure? Basically, I'm trying to get the text and the swap image to change as soon as the first image moves, as described in the example.

3) setting the delegate for the CABasicAnimation doesn't have any effect, although it does for the CAAnimationGroup: as a result, you can't manage events like animationDidStop for every single animation, just for the whole group. Is there any alternative way to do so?

4) following from 3), is it possible, using CAAnimationGroup, to intercept the events to create a stop/start behaviour? Let's suppose I wanted to have play/stop buttons, and to resume the animation from exactly where I had left it?

As a conclusive question, I would simply like to know if anyone did something similar and, most importantly, if this way of doing things (using a CAAnimationGroup) is actually the way to go or if it's better to use CAKeyFrameAnimation or something else.


回答1:


I managed to solve the problem, albeit with some workarounds.

1) The trick here is that

removedOnCompletion = NO;
fillMode = kCAFillModeForwards;

need to be assigned to every single animation, not to the CAAnimationGroup.

2) This requires a separate animation to be determined for the CATextLayer, and animating the "string" property. In this case, the code was right except for the "contents" key-value which should have been

[text addAnimation:textgroup forKey:@"contentAnimate"];

Alternatively, see point 3. The callback way of operation allows to change a label.text.

3) The only way to do this is to actually not use the CAAnimationGroup and set up a sequence of CABasicAnimation. The general schema is: create the first CABasicAnimation in a function, assign the delegate. In the animationDidStop method, add a callback to the function that creates the CABasicAnimation. This way, each animation will be created. At the same time, this allows to intercept and react to specific events within the animation.

-(void)performNextAnimation:(int)index {
    // ... this gives for granted you have an object for #index ...
    NSDictionary* thisEvent = [self.events objectAtIndex:index];
    float prev_x = [thisEvent valueForKey:@"prev_x"];
    float prev_y = [thisEvent valueForKey:@"prev_x"];
    float actual_x = [thisEvent valueForKey:@"prev_x"];
    float actual_y = [thisEvent valueForKey:@"prev_x"];
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];            
    GPoint startPt = CGPointMake(prev_x,prev_y);
    CGPoint endPt = CGPointMake(actual_x, actual_y);
    anim.duration = 0.5;
    anim.fromValue = [NSValue valueWithCGPoint:startPt];
    anim.toValue = [NSValue valueWithCGPoint:endPt];
    anim.beginTime = start;
    [anim setDelegate:self];
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];            
    [object addAnimation:anim forKey:@"position"];
}

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    // ... your conditions go here ...
    // ... your actions go here ... 
    //     e.g. set label.text
    [self performNextAnimation];
}

4) Following from 3, this is not possible in CAAnimationGroup. Reading the docs, I would say that CAAnimationGroup is not intended to be used for sequences in which each event represents a step forward in time (for this you need to use CAKeyFrameAnimation or a sequence of CABasicAnimation with a callback function, as I did). CAAnimationGroup is intended for linked animation that should be executed on an all-or-nothing basis (similarly to CATransaction).



来源:https://stackoverflow.com/questions/9559377/synchronising-image-text-and-positioning-with-coreanimation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!