核心动画CALayer蒙版动画性能

20

我们希望在我们的iPhone应用中使用UITabBar,但有一个例外:我们有一个“同步”按钮,我希望在同步操作正在进行时旋转它。

alt text

很不幸,这意味着我需要创建一个自定义的选项卡栏,但那无关紧要:使用核心动画实现的动画看起来很棒。问题是,在动画过程中,它会对屏幕上所有其他使用动画的东西产生负面影响:UITableView 滚动、MKMapView 平移和标记放置等。我的测试设备是 iPhone 4。
问题似乎在于我如何实现选项卡栏 - 我想实现与 UITabBar 非常相似的效果,只需提供图标的 PNG 文件,然后利用 alpha 通道通过遮罩背景图像来创建正常和突出显示的状态。我使用 CALayer 的 mask 属性完成了这个目标:
// 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
}

所以这一切都很好,除了性能。你可以看到我已经尝试了谷歌上关于栅格化图层/使其不透明的提示-没有帮助。
我认为我已经确定了遮罩层是罪魁祸首。当我取出遮罩层,只旋转fgLayer而不是它的遮罩时,性能非常好,尽管这肯定不是我想要的效果:

alt text

如果我在应用蒙版时旋转fgLayer而不是蒙版同时,性能仍然像以前一样糟糕。

因此,如果每帧动画都必须重新合成蒙版导致速度变慢,是否有其他技术可以使用以实现类似的效果并具有更好的性能?使用路径而不是图像作为蒙版层?还是我必须降低到OpenGL或其他东西才能获得良好的性能?

更新:进一步强化了蒙版是减速的想法,我的同事建议尝试仅使用图像作为内容旋转CALayer,类似于上面的示例,没有蒙版,这样性能也很好。所以我只能做一个纯色(没有渐变),但这可能是一个很好的临时解决方案。我仍然希望能够以良好的性能旋转蒙版,因此欢迎提出建议:)

2个回答

15

Brent,

你为什么需要使用图层蒙版?难道不能将你的蒙版图层转换为子图层吗?你只需要确保你的图片有正确的 alpha 值,并使用它的 CGImageRef 作为该图层的内容。

还有一件事。我还没有弄清楚原因,但我也注意到当我在每个图层上应用 shouldRasterize 时会出现性能问题,而不是只在顶部图层上应用。你可以尝试删除对 mask 图层中的 setShouldRasterize:YES 的引用,看看是否有帮助。


1
你的回答的第二部分是解决方案 - 我应该在顶层而不是子层上设置shouldRasterize。这似乎使性能与没有遮罩时一样好。我“需要”一个遮罩的原因是为了可以用漂亮的渐变来“填充”简单的PNG图标,以增加视觉效果。默认的UITabBar也是这样做的,所以我想做同样的事情(但是用黄色)。 - Brent Dillingham
很好,Brent。很高兴它是那么简单。如果你对这个问题的原因有任何见解,我会很乐意知道。;-) - Matt Long
1
我也很想知道为什么。此外,我真的很想知道如何将顶部图层转换为光栅图会如何提高蒙版动画的性能。它仍然按照我想要的方式运行 - 背景渐变图像保持静止,而蒙版正在旋转。它是否在动画开始之前实际上为每个帧预计算位图?看起来像魔法。在这种情况下,我喜欢魔法。 :) - Brent Dillingham
1
我也想知道这里发生了什么。假设我正确地阅读了您的评论,并且您只在包含视图的图层上设置了 shouldRasterize,也许 CA 会采取 shouldRasterize 提示并将每个子图层的遮罩操作合并为一个操作,从而使其无需为每个遮罩图层交换缓冲区。您是否尝试在两种情况下都打开 Instruments 中的“关闭屏幕颜色”选项,以查看 CA 进行了多少次渲染通道?如果我有时间,我会测试它,但是如果您比我更快,请告诉我。我想揭开这个黑魔法的神秘面纱。 :) - Nathan Eror
我有同样的经历,我认为 shouldRasterize 应该改善动画,但实际上它却导致了卡顿。我进行了检测,发现整个图层(带有 shouldRasterize 的那个)都是离屏渲染,这就导致了问题。PS. 我知道这是一个旧帖子。 - Shuo

7
一种方法是创建一个CAShapeLayer作为蒙版,您需要用贝塞尔路径制作一个“同步”图标的版本,但形状图层每次变换的性能成本要低得多。不幸的是,您不能确定旋转是否导致性能问题 - 这很可能是遮罩引起的大部分延迟,如果是这样,那么您将为了很少的好处而进行所有矢量化工作。
我认为最好的解决方案是使用UIImage的动画功能:创建一个电影帧,其中包含图标旋转动画的每个帧,并在选项卡栏中显示该动画的UIImage。虽然不是最优雅的解决方案,但系统中的许多动画(例如邮件和备忘录垃圾桶“删除”图标和各种形式的活动指示器)都是以相同的方式实现的。

2
谢谢您的回答——使用动画的电影胶片肯定是我在考虑的事情,但听起来需要很多工作和文件空间。幸运的是,在superlayer上使用shouldRasterize似乎已经达到了我期望的性能。 - Brent Dillingham
从答案来看,似乎“将 shouldRasterize 设置为 NO”解决了你的问题。 - Shuo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接