Синхронизация изображения, текста и позиционирования с CoreAnimation

Я немного новичок в анимации и пару дней экспериментировал с CoreAnimation. Не стесняйтесь предупредить меня, если этот вопрос не имеет смысла, но я пытаюсь добиться следующего. У меня есть три объекта:

  • должно быть изображение, движущееся по заданному шаблону
  • один должен быть UIImage, который меняет местами два изображения
  • должен быть текст (CATextLayer?), содержимое которого изменяется

Три действия должны выполняться синхронно.

В качестве примера представьте программу, показывающую синусоидальную функцию, как в ЭКГ, колеблющуюся между -1 и +1: первое изображение затем будет перемещаться в соответствии с текущим значением (-1, -0.9, -0.8, ... 0, +0,1, +0,2, ... 1) изображение подкачки будет показывать «+» для положительных значений и «-» для отрицательных значений, а текст будет чередоваться между «Положительными» и «Отрицательными».

Я пробовал с CAAnimationGroup, но мне явно чего-то не хватает. Некоторый код:

{
    // 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];


}

Теперь ... проблемы:

1) изображение подкачки возвращается к исходному при каждой подкачке. Итак, если я поменяю местами A-> B, я вижу, что он переходит от A к B в ожидаемое время, но затем возвращается к A. Я прочитал несколько потоков на SO об этом, но не смог заставить его работать .

2) изменение строки текстового слоя по времени ... возможно ли это с этой инфраструктурой? По сути, я пытаюсь изменить текст и изображение подкачки при перемещении первого изображения, как описано в примере.

3) установка делегата для CABasicAnimation не имеет никакого эффекта, хотя имеет значение для CAAnimationGroup: в результате вы не можете управлять такими событиями, как animationDidStop для каждой отдельной анимации, только для всей группы. Есть ли альтернативный способ сделать это?

4) следует из 3), можно ли с помощью CAAnimationGroup перехватить события для создания поведения остановки / запуска? Предположим, я хотел иметь кнопки воспроизведения / остановки и возобновлять анимацию с того места, где я ее оставил?

В качестве решающего вопроса я просто хотел бы знать, делал ли кто-нибудь что-то подобное и, что наиболее важно, действительно ли этот способ делать что-то (с использованием CAAnimationGroup) действительно подходит или лучше использовать CAKeyFrameAnimation или что-то еще.


person puntofisso    schedule 04.03.2012    source источник
comment
Добро пожаловать в сообщество Stack Overflow @puntofisso ...   -  person Mark Iliffe    schedule 05.03.2012


Ответы (1)


Мне удалось решить проблему, хотя и с некоторыми обходными путями.

1) Уловка здесь в том, что

removedOnCompletion = NO;
fillMode = kCAFillModeForwards;

должны быть назначены для каждой отдельной анимации, а не для CAAnimationGroup.

2) Это требует определения отдельной анимации для CATextLayer и анимации свойства «строка». В этом случае код был правильным, за исключением пары "содержимое", которая должна была быть

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

В качестве альтернативы см. Пункт 3. Обратный вызов позволяет изменить label.text.

3) Единственный способ сделать это - фактически не использовать CAAnimationGroup и настроить последовательность CABasicAnimation. Общая схема такова: создайте первую CABasicAnimation в функции, назначьте делегата. В методе animationDidStop добавьте обратный вызов функции, которая создает CABasicAnimation. Таким образом будет создана каждая анимация. В то же время это позволяет перехватывать определенные события в анимации и реагировать на них.

-(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) Согласно пункту 3, это невозможно в CAAnimationGroup. Читая документацию, я бы сказал, что CAAnimationGroup не предназначена для использования для последовательностей, в которых каждое событие представляет собой шаг вперед во времени (для этого вам нужно использовать CAKeyFrameAnimation или последовательность CABasicAnimation с функцией обратного вызова, как это сделал я). CAAnimationGroup предназначена для связанной анимации, которая должна выполняться по принципу «все или ничего» (аналогично CATransaction).

person puntofisso    schedule 07.03.2012