Плавно анимированные отверстие в CALayer?


Хорошо, я выяснил, как это сделать, основываясь на различных сообщениях здесь на SO, и это отлично работает. Я работаю над наложением, которое будет в основном маскировать все окно, за исключением небольшой области. Это делается для привлечения внимания к определенной области моего приложения. Я использую кучу вызовов moveToPoint: и addLineToPoint:, как это (это в моем подклассе CALayer' drawInContext:):

....

// inner path (CW)
[holePath moveToPoint:CGPointMake(x, y)];
[holePath addLineToPoint:CGPointMake(x + w, y)];
[holePath addLineToPoint:CGPointMake(x + w, y + h)];
[holePath addLineToPoint:CGPointMake(x, y+h)];

// outer path (CCW)
[holePath moveToPoint:CGPointMake(xBounds, yBounds)];
[holePath addLineToPoint:CGPointMake(xBounds, yBounds + hBounds)];
[holePath addLineToPoint:CGPointMake(xBounds + wBounds, yBounds + hBounds)];
[holePath addLineToPoint:CGPointMake(xBounds + wBounds, yBounds)];

// put the path in the context
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0, 0);
CGContextAddPath(ctx, holePath.CGPath);
CGContextClosePath(ctx);

// set the color
CGContextSetFillColorWithColor(ctx, self.overlayColor.CGColor);

// draw the overlay
CGContextDrawPath(ctx, kCGPathFillStroke);

(holePath является примером UIBezierPath.)

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

-(CABasicAnimation *)makeAnimationForKey:(NSString *)key {
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key];
    anim.fromValue = [[self presentationLayer] valueForKey:key];
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    anim.duration = 0.5;

    return anim;
}

И переехал actionForKey:, initWithLayer: и needsDisplayForKey: (возвращая результат makeAnimationForKey: в actionForKey:. Теперь я получаю хороший "слой дыры", который имеет свойство holeRect, которое анимируется с помощью неявных Каанимаций! К сожалению, это очень неспокойно. Я получаю что-то вроде 2 или 3 кадров в секунду. Я подумал, что, возможно, проблема была в фоновом режиме, и попытался заменить его снимком, но без костей. Затем я использовал инструменты для профилирования и обнаружил что огромный боров здесь-это призыв к CGContextDrawPath().

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

4 2

4 ответа:

Хорошо, я попробовал предложение phix23, и оно полностью сделало трюк! Сначала я подклассировал CALayer и добавил CAShapeLayer в качестве подслоя, но я не мог заставить его работать должным образом, и я очень устал на этом этапе, поэтому я сдался и просто заменил свой подкласс полностью на CAShapeLayer! Я использовал приведенный выше код в своем собственном методе, возвращая UIBezierPath, и анимировал так:

UIBezierPath* oldPath = [self pathForHoleRect:self.holeRect];
UIBezierPath* newPath = [self pathForHoleRect:rectToHighlight];
self.holeRect = rectToHighlight;

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 0.5;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (id)oldPath.CGPath;
animation.toValue = (id)newPath.CGPath;
[self.holeLayer addAnimation:animation forKey:@"animatePath"];
self.holeLayer.path = newPath.CGPath;

Интересное примечание - path не является имплицитно анимируемым. Думаю, в этом есть смысл.

Перерисовка стоит дорого, анимация-нет. Когда вы устанавливаете значение для "holeRect", вы даете команду всему слою перерисоваться. ЭТО НЕ ТО ЖЕ САМОЕ, ЧТО ТИПИЧНАЯ АНИМАЦИЯ СВОЙСТВ С ОПТИМИЗАЦИЕЙ ПРОИЗВОДИТЕЛЬНОСТИ. Если размер слоя равен размеру всего экрана, то вы фактически воссоздаете новую версию слоя на каждом кадре. Это одна из вещей, которые apple делает, чтобы обеспечить плавную анимацию. Вы хотите, чтобы предотвратить перерисовки как можно больше.

Я бы предложил создайте слой с отверстием в центре и убедитесь, что слой достаточно велик, чтобы покрыть весь экран независимо от того, где отверстие центрировано. Затем анимация должна анимировать свойство" положение " слоя. Хотя может показаться расточительным иметь этот массивный слой, из которого только ~ 30% когда-либо будет использоваться сразу, он гораздо менее расточителен, чем перерисовка на каждом кадре. Если вы хотите сохранить свой интерфейс "holeRect", вы можете, но слой со свойством "holeRect" должен содержат подслой или подслои, которые анимированы так, как я описал (в layoutSubviews, а не drawInContext:).

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

Я публикую anwser, потому что я не могу прокомментировать ваш вопрос (пока) из-за репутации.

Мой вопрос заключается в том, нужно ли это создавать программно? Если речь идет только о создании зоны внимания, вы можете использовать другой подход.

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

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

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

Многоуровневый подходМногослойный подход, непрямоугольное отверстие