Анимировать границы CALayer с перерисовкой

Мне интересно, как можно анимировать границы CALayer, чтобы при каждом изменении границ слой вызывал drawInContext:. Я пробовал два следующих метода в моем подклассе CALayer:

  • Установка needsDisplayOnBoundsChange на YES
  • Возврат YES для + (BOOL)needsDisplayForKey:(NSString*)key для bounds ключа

Ни работать. CALayer, кажется, полон решимости использовать исходное содержимое слоя и просто масштабировать его в соответствии с contentsGravity (что, я полагаю, для повышения производительности). Является ли это обходным путем для этого, или я упускаю что-то очевидное?

РЕДАКТИРОВАТЬ: И, кстати, я заметил, что мой собственный подкласс CALayer не вызывает initWithLayer: для создания presentationLayer - странно.

Заранее спасибо, Сэм


person Sam    schedule 27.09.2011    source источник
comment
Еще одна вещь, которая не работает: создание подклассов и переопределение setFrame, setBounds и setPosition. Они не вызываются во время анимации.   -  person Alexander Ljungberg    schedule 29.10.2011
comment
Я вас не совсем понимаю. Что вы пытаетесь оживить? Просто границы CALayer или что-то еще? Анимация границ - довольно простая задача, покадровая - более сложная.   -  person beryllium    schedule 31.10.2011
comment
Представьте, что ваш слой содержит что-то вроде кнопки со сложной, но независимой от размера графической рамкой. Если вы анимируете его так, чтобы сказать удвоение ширины, он будет анимироваться с использованием масштабирования растрового изображения, растягиваясь и пикселизируя на протяжении всей анимации, даже если у вас needsDisplayOnBoundsChange ДА. Только последний кадр будет правильно отрисован с drawInContext:.   -  person Alexander Ljungberg    schedule 02.11.2011


Ответы (5)


Вы можете использовать метод , описанный здесь: переопределите метод +needsDisplayForKey: CALayer и он будет перерисовывать свое содержимое на каждом этапе анимации.

person titaniumdecoy    schedule 31.10.2011
comment
Это отличный ответ. Предоставьте дополнительную информацию о ссылке, чтобы мы знали, прежде чем мы нажмем. - person Alexsander Akers; 02.11.2011
comment
Насколько я могу судить, этот метод действительно вызывает метод перерисовки для каждого кадра, хотя, как ни странно, CALayer не отображает результаты этих перерисовок, а продолжает сгенерированный заранее растянутый контент. Тем не менее, это значительный шаг вперед. - person Alexander Ljungberg; 04.11.2011
comment
@AlexanderLjungberg: этот метод должен перерисовывать контент на каждом этапе анимации. Я создал тестовый проект, чтобы убедиться, что он работает. Я не уверен, из-за чего ваш слой может не отображаться повторно. - person titaniumdecoy; 04.11.2011
comment
@titaniumdecoy: Я тоже пробовал ... Он работает для настраиваемых свойств, но не для bounds (как и для других свойств CALayer). Метод actionForKey: вызывается для настраиваемых свойств, но не для свойств CALayer. Вы анимируете настраиваемое свойство или свойство CALayer? - person debleek63; 14.11.2011
comment
Мне удалось анимировать настраиваемое свойство (радиус шара внутри квадратного CALayer), которое анимировалось при изменении положения представления. - person titaniumdecoy; 15.11.2011
comment
Если вы проголосуете за этот ответ, пожалуйста, проголосуйте и за связанный ответ. Спасибо. - person titaniumdecoy; 30.11.2011
comment
Итак, если +needsDisplayForKey: не работает с bounds, что тогда? - person zakdances; 14.04.2013

Я не уверен, что установка viewFlags будет эффективной. Второе решение точно не сработает:

Реализация по умолчанию возвращает NO. Подклассы должны * вызывать super для свойств, определенных суперклассом. (Например, * не пытайтесь вернуть YES для свойств, реализованных CALayer, * выполнение будет иметь неопределенные результаты.)

Вам необходимо установить режим содержимого представления на UIViewContentModeRedraw:

UIViewContentModeRedraw,    //redraw on bounds change (calls -setNeedsDisplay)

Ознакомьтесь с документацией Apple по предоставлению контента с помощью CALayer < / а>. Они рекомендуют использовать свойство делегата CALayer вместо подкласса, что может быть намного проще, чем то, что вы сейчас пытаетесь.

person Ash Furrow    schedule 31.10.2011
comment
UIViewContentModeRedraw не вызывает перерисовку содержимого слоя на каждом этапе анимации. - person titaniumdecoy; 31.10.2011
comment
Действительно? Хммм ... Вы знаете, вызываются ли методы CALayerDelegate во время анимации? - person Ash Furrow; 31.10.2011
comment
Нет, если вы не используете метод, который я указал в своем ответе (который должен переопределить метод +needsDisplayForKey: CALayer) - person titaniumdecoy; 31.10.2011
comment
Это не работает, потому что границы не обновляются постоянно во время анимации. Он изменяется только один раз в начале анимации. - person zakdances; 17.04.2013

Я не знаю, полностью ли это подходит для решения вашего вопроса, но мне удалось заставить это работать.

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

Затем я переопределил метод -display моего слоя INSTEAD OF -drawInContext:. В нем я установил contents на предварительно обработанное CGImage, и это сработало.

Наконец, вашему слою также необходимо изменить значение по умолчанию contentsGravity на что-то вроде @"left", чтобы изображение содержимого не масштабировалось.

Проблема, с которой я столкнулся, заключалась в том, что контекст, передаваемый в -drawInContext:, имел начальный размер слоя, а не конечный размер после анимации. (Вы можете проверить это с помощью методов CGBitmapContextGetWidth и CGBitmapContextGetHeight.)

Мои методы по-прежнему вызываются только один раз для всей анимации, но установка содержимого слоя напрямую с помощью метода -display позволяет передавать изображение, размер которого превышает видимые границы. Метод drawInContext: не позволяет этого, поскольку вы не можете рисовать за пределами контекста CGContext.

Подробнее о различиях между различными методами рисования слоев см. http://www.apeth.com/iOSBook/ch16.html

person bcattle    schedule 05.11.2015

Недавно я столкнулся с той же проблемой. Вот что я выяснил:

Есть трюк, который вы можете сделать, это анимация теневой копии таких границ, как:

var shadowBounds: CGRect {
  get { return bounds }
  set { bounds = newValue}
}

затем переопределите CALayer + needsDisplayForKey :.

Однако это НЕ МОЖЕТ быть тем, что вы хотите сделать, если ваш рисунок зависит от границ. Как вы уже заметили, основная анимация просто масштабирует содержимое слоя для анимации границ. Это верно, даже если вы выполните описанный выше трюк, то есть содержимое масштабируется, даже если границы изменились во время анимации. В результате ваша анимация рисунков выглядит противоречивой.

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

person user8062943    schedule 16.07.2017

Это ваш собственный класс:

@implementation MyLayer

-(id)init
{
    self = [super init];
    if (self != nil)
        self.actions = [NSDictionary dictionaryWithObjectsAndKeys:
                        [NSNull null], @"bounds",
                        nil];
    return self;
}

-(void)drawInContext:(CGContextRef)context
{
    CGContextSetRGBFillColor(context,
                             drand48(),
                             drand48(),
                             drand48(),
                             1);
    CGContextFillRect(context,
                      CGContextGetClipBoundingBox(context));
}

+(BOOL)needsDisplayForKey:(NSString*)key
{
    if ([key isEqualToString:@"bounds"])
        return YES;
    return [super needsDisplayForKey:key];
}

@end

Это дополнения к шаблону по умолчанию xcode 4.2:

-(BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

        // create and add layer
    MyLayer *layer = [MyLayer layer];
    [self.window.layer addSublayer:layer];
    [self performSelector:@selector(changeBounds:)
               withObject:layer];

    return YES;
}

-(void)changeBounds:(MyLayer*)layer
{
        // change bounds
    layer.bounds = CGRectMake(0, 0,
                              drand48() * CGRectGetWidth(self.window.bounds),
                              drand48() * CGRectGetHeight(self.window.bounds));

        // call "when idle"
    [self performSelector:@selector(changeBounds:)
               withObject:layer
               afterDelay:0];
}

----------------- отредактировал:

Хорошо ... это не то, о чем вы просили :) Извините: |

----------------- отредактировано (2):

И зачем вам что-то подобное? (void)display можно использовать, но в документации сказано, что он предназначен для настройки _4 _...

person debleek63    schedule 01.11.2011
comment
Это не работает: + needsDisplayForKey не вызывает перерисовку границ - person Eyal Redler; 06.05.2012