UIView масштабирование во время анимации - PullRequest
6 голосов
/ 04 мая 2009

У меня есть пользовательский UIView для отображения мозаичного изображения.

    - (void)drawRect:(CGRect)rect
    {
        ...
        CGContextRef context = UIGraphicsGetCurrentContext();       
        CGContextClipToRect(context,
             CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));      
        CGContextDrawTiledImage(context, imageRect, imageRef);
        ...
    }

Теперь я пытаюсь оживить изменение размера этого представления.

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];

// change frame of view

[UIView commitAnimations];

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

Почему CA пытается масштабировать? Как я могу предотвратить это? Что я пропустил?

Ответы [ 6 ]

15 голосов
/ 05 мая 2009

Если бы Core Animation пришлось возвращаться к вашему коду для каждого кадра анимации, это никогда не было бы так быстро, как есть, а анимация пользовательских свойств была давно запрашиваемой функцией и часто задаваемыми вопросами для CA на Mac.

Использование UIViewContentModeRedraw на правильном пути, а также лучшее, что вы получите от CA. Проблема заключается в том, что с точки зрения UIKit фрейм имеет только два значения: значение в начале перехода и значение в конце перехода, и это то, что вы видите. Если вы посмотрите документы архитектуры Core Animation, вы увидите, как CA имеет частное представление всех свойств слоя и их значений, изменяющихся со временем. Вот где происходит интерполяция кадров, и вы не сможете получать уведомления об изменениях по мере их возникновения.

Таким образом, единственный способ - использовать NSTimer (или performSelector:withObject:afterDelay:) для изменения рамки просмотра с течением времени, по старинке.

6 голосов
/ 12 декабря 2010

Возможно, вы захотите взглянуть на http://www.nomadplanet.fr/2010/11/animate-calayer-custom-properties-with-coreanimation/

4 голосов
/ 08 июля 2010

Я столкнулся с подобной проблемой в другом контексте, я наткнулся на эту тему и нашел предложение Дункана об установке фрейма в NSTimer. Я реализовал этот код для достижения того же:

CGFloat kDurationForFullScreenAnimation = 1.0;
CGFloat kNumberOfSteps = 100.0; // (kDurationForFullScreenAnimation / kNumberOfSteps) will be the interval with which NSTimer will be triggered.
int gCurrentStep = 0;
-(void)animateFrameOfView:(UIView*)inView toNewFrame:(CGRect)inNewFrameRect withAnimationDuration:(NSTimeInterval)inAnimationDuration numberOfSteps:(int)inSteps
{
    CGRect originalFrame = [inView frame];

    CGFloat differenceInXOrigin = originalFrame.origin.x - inNewFrameRect.origin.x;
    CGFloat differenceInYOrigin = originalFrame.origin.y - inNewFrameRect.origin.y;
    CGFloat stepValueForXAxis = differenceInXOrigin / inSteps;
    CGFloat stepValueForYAxis = differenceInYOrigin / inSteps;

    CGFloat differenceInWidth = originalFrame.size.width - inNewFrameRect.size.width;
    CGFloat differenceInHeight = originalFrame.size.height - inNewFrameRect.size.height;
    CGFloat stepValueForWidth = differenceInWidth / inSteps;
    CGFloat stepValueForHeight = differenceInHeight / inSteps;

    gCurrentStep = 0;
    NSArray *info = [NSArray arrayWithObjects: inView, [NSNumber numberWithInt:inSteps], [NSNumber numberWithFloat:stepValueForXAxis], [NSNumber numberWithFloat:stepValueForYAxis], [NSNumber numberWithFloat:stepValueForWidth], [NSNumber numberWithFloat:stepValueForHeight], nil];
    NSTimer *aniTimer = [NSTimer timerWithTimeInterval:(kDurationForFullScreenAnimation / kNumberOfSteps)
                                                target:self
                                              selector:@selector(changeFrameWithAnimation:)
                                              userInfo:info
                                               repeats:YES];
    [self setAnimationTimer:aniTimer];
    [[NSRunLoop currentRunLoop] addTimer:aniTimer
                                 forMode:NSDefaultRunLoopMode];
}

-(void)changeFrameWithAnimation:(NSTimer*)inTimer
{
    NSArray *userInfo = (NSArray*)[inTimer userInfo];
    UIView *inView = [userInfo objectAtIndex:0];
    int totalNumberOfSteps = [(NSNumber*)[userInfo objectAtIndex:1] intValue];
    if (gCurrentStep<totalNumberOfSteps)
    {
        CGFloat stepValueOfXAxis = [(NSNumber*)[userInfo objectAtIndex:2] floatValue];
        CGFloat stepValueOfYAxis = [(NSNumber*)[userInfo objectAtIndex:3] floatValue];
        CGFloat stepValueForWidth = [(NSNumber*)[userInfo objectAtIndex:4] floatValue];
        CGFloat stepValueForHeight = [(NSNumber*)[userInfo objectAtIndex:5] floatValue];

        CGRect currentFrame = [inView frame];
        CGRect newFrame;
        newFrame.origin.x = currentFrame.origin.x - stepValueOfXAxis;
        newFrame.origin.y = currentFrame.origin.y - stepValueOfYAxis;
        newFrame.size.width = currentFrame.size.width - stepValueForWidth;
        newFrame.size.height = currentFrame.size.height - stepValueForHeight;

        [inView setFrame:newFrame];

        gCurrentStep++;
    }
    else 
    {
        [[self animationTimer] invalidate];
    }
}

Я обнаружил, что установка кадра и таймера занимает много времени. Вероятно, это зависит от того, насколько глубока иерархия представления, на которой вы вызываете -setFrame, используя этот подход. И, вероятно, это причина того, что представление сначала изменяется в размере, а затем анимируется в начало координат в SDK. Или, может быть, есть какая-то проблема в механизме таймера в моем коде, из-за которой снижается производительность?

Это работает, но очень медленно, возможно, из-за слишком глубокой иерархии моих представлений.

2 голосов
/ 09 мая 2009

Как указывает Дункан, Core Animation не будет перерисовывать содержимое слоя UIView каждый кадр при изменении его размера. Вы должны сделать это самостоятельно, используя таймер.

Ребята из Omni опубликовали хороший пример того, как создавать анимацию на основе ваших собственных пользовательских свойств, которые могут быть применимы к вашему делу. Этот пример, вместе с объяснением того, как он работает, можно найти здесь .

1 голос
/ 04 июня 2014

Я наткнулся на этот вопрос при попытке анимировать изменение размера в UIView с рамкой. Я надеюсь, что другие, которые также прибудут сюда, получат пользу от этого ответа. Первоначально я создавал пользовательский подкласс UIView и переопределял метод drawRect следующим образом:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetLineWidth(ctx, 2.0f);
    CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor);
    CGContextStrokeRect(ctx, rect);

    [super drawRect:rect];
}

Это привело к проблемам с масштабированием при объединении с анимацией, как уже упоминали другие. Верх и низ границы станут слишком толстыми или двумя тонкими и будут выглядеть чешуйчатыми. Этот эффект легко заметен при переключении на медленную анимацию.

Решением было отказаться от пользовательского подкласса и использовать вместо этого следующий подход:

[_borderView.layer setBorderWidth:2.0f];
[_borderView.layer setBorderColor:[UIColor orangeColor].CGColor];

Исправлена ​​проблема с масштабированием границы на UIView во время анимации.

1 голос
/ 15 апреля 2013

Если это не большая анимация или производительность не влияет на продолжительность анимации, вы можете использовать CADisplayLink. Это сглаживает анимацию масштабирования, например, пользовательского UIView-чертежа UIBezierPaths. Ниже приведен пример кода, который вы можете адаптировать к своему изображению.

Ивари моего пользовательского представления содержат displayLink как CADisplayLink и два CGRect: toFrame и fromFrame, а также duration и startTime.

- (void)yourAnimationMethodCall
{
    toFrame = <get the destination frame>
    fromFrame = self.frame; // current frame.

    [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // just in case    
    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateFrame:)];
    startTime = CACurrentMediaTime();
    duration = 0.25;
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)animateFrame:(CADisplayLink *)link
{
    CGFloat dt = ([link timestamp] - startTime) / duration;

    if (dt >= 1.0) {
        self.frame = toFrame;
        [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        displayLink = nil;
        return;
    }

    CGRect f = toFrame;
    f.size.height = (toFrame.size.height - fromFrame.size.height) * dt + fromFrame.size.height;
    self.frame = f;
}

Обратите внимание, что я не тестировал его производительность, но он работает без сбоев.

...