Core Animation CALayer - производительность анимации маски - PullRequest
20 голосов
/ 23 декабря 2010

Мы хотели использовать UITabBar в нашем приложении для iPhone, но с одним исключением: у нас есть кнопка «синхронизация», которую я хотел повернуть во время операции синхронизации.

alt text

К сожалению, это означало необходимость создания настраиваемой панели вкладок, но это ни здесь, ни там: анимация, которую я реализовал с помощью Core Animation, выглядит потрясающе. Проблема в том, что при анимации это отрицательно сказывается на производительности всего остального с помощью анимации на экране: прокрутка UITableView, панорамирование MKMapView, удаление булавок и т. Д. Моим тестовым устройством является iPhone 4.

Проблема, похоже, заключается в том, как я реализовал панель вкладок - я хотел добиться чего-то очень похожего на UITabBar, где вы просто предоставляете PNG для значка, и он использует альфа-канал для создания нормальных и выделенных состояний с помощью маскировка фонового изображения. Я сделал это с помощью свойства mask CALayer:

// Inside a UIView subclass' init method...

// Create the mask layer by settings its contents as our PNG icon.
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0, 0, 31, 31);
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.contentsScale = [[UIScreen mainScreen] scale];
maskLayer.rasterizationScale = [[UIScreen mainScreen] scale];
maskLayer.contents = (id)symbolImage.CGImage;
maskLayer.shouldRasterize = YES;
maskLayer.opaque = YES;

fgLayer = [[CALayer layer] retain];
fgLayer.frame = self.layer.frame;
fgLayer.backgroundColor = [UIColor colorWithImageNamed:@"tabbar-normal-bg.png"].CGColor;
fgLayer.mask = maskLayer; // Apply the mask
fgLayer.shouldRasterize = YES;
fgLayer.opaque = YES;

[self.layer addSublayer:fgLayer];

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

Для анимации я просто вращаю слой маски:

- (void)startAnimating {
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath: @"transform"];
    CATransform3D transform = CATransform3DMakeRotation(RADIANS(179.9), 0.0, 0.0, 1.0);
    animation.toValue = [NSValue valueWithCATransform3D:transform];
    animation.duration = 5;
    animation.repeatCount = 10000;
    animation.removedOnCompletion = YES;
    [fgLayer.mask addAnimation:animation forKey:@"rotate"]; // Add animation to the mask
}

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

Я думаю, что определил слой маски как виновника. Когда я вынимаю слой маски и просто поворачиваю fgLayer вместо его маски, производительность замечательна, хотя это, конечно, не тот эффект, которого я добиваюсь:

alt text

Производительность так же плоха, как и раньше, если я поверну fgLayer вместо маски , а маска будет применена.

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

ОБНОВЛЕНИЕ: Еще больше подкрепляя идею о том, что маска - это замедление, мой коллега предложил попытаться повернуть CALayer только с изображением в качестве содержимого - так похоже на мой пример выше без маски - - и производительность была так же хороша. Так что я могу сделать только сплошной цвет (без градиента), но это может быть хорошим промежуточным решением. Я все еще хотел бы добиться поворота маски с хорошей производительностью, поэтому предложения приветствуются:)

Ответы [ 2 ]

15 голосов
/ 23 декабря 2010

Brent,

Зачем вам нужна маска слоя?Разве вы не можете просто преобразовать свой слой маски в подслой?Вам просто нужно убедиться, что у вашего изображения правильная альфа, и вы будете использовать его CGImageRef в качестве содержимого этого слоя.

Еще одна вещь.Я еще не понял, почему, но я также заметил проблемы с производительностью, когда я применяю shouldRasterize к каждому слою, а не только к верхнему слою.Вы можете увидеть, помогает ли вообще удаление ссылки на setShouldRasterize: YES в слое маски.

7 голосов
/ 23 декабря 2010

Один из подходов состоит в том, чтобы создать CAShapeLayer для использования в качестве маски - вам понадобится немало усилий, чтобы создать версию значка «Синхронизация» с путями Безье, но слои фигур подвергаются гораздо более низкая стоимость производительности на преобразование, чем у растровых. К сожалению, вы не можете быть уверены в том, что из-за поворота возникает проблема с производительностью - вполне может быть маскировка, вызывающая большую часть задержки, и в этом случае вы сделаете всю эту векторизацию для небольшой выгоды. *

Я думаю, что лучшим решением является использование возможностей анимации UIImage: создайте полосу из каждого кадра анимации вращения значка и просто отобразите анимацию UIImage на панели вкладок. Это не самое элегантное решение, но ряд анимаций в системе - например, значок «Удалить» в корзине «Почта» и «Примечания» и различные формы индикатора активности - реализованы одинаково.

...