我正在尝试将 CADisplayLink 和 CAAnimation 结合起来,以便我可以使用 CAAnimation 为某些图层设置动画,并根据动画图层的变化表示层实时调整其他图层。我意识到当 CADisplayLink 回调在我的进程中时,CAAnimations 是在我的进程之外驱动的,因此同步永远不会完全紧密。但是,我希望它会比我所看到的更好(我看到的延迟可能高达 4 帧)。
归结为一些简单的示例代码,我创建了一堆层对。其中一半是轮廓,另一半是实心方块 - 所有项目都具有相同的大小。目标是为这些矩形对设置动画,以使它们的轮廓和正方形始终在一起(即同一帧)。
每个循环,我为实体层设置一个新位置,并让 CADisplayLink 回调根据其互补实体层的表示层位置设置轮廓层。在模拟器上,这似乎大部分时间都有效,至少对于少数层。一旦我得到多达 24 对图层,一些轮廓开始滞后。在设备(iPad Air 和 iPhone 6)上,即使只有一个正方形,轮廓也会滞后。我正在使用 iOs 8 进行所有测试。
下面是我的 ViewController 实现。
@implementation ViewController
{
CADisplayLink *_displayLink;
NSUInteger _startingLayerIndex;
}
#define NUMLAYERS 1
- (void)viewDidLoad {
[super viewDidLoad];
_startingLayerIndex = self.view.layer.sublayers.count;
for (int i=0; i<NUMLAYERS; ++i) {
CALayer *outlineLayer = [[CALayer alloc] init];
outlineLayer.borderColor = [[UIColor blackColor] CGColor];
outlineLayer.borderWidth = 1.0;
CALayer *fillLayer = [[CALayer alloc] init];
fillLayer.backgroundColor = [[UIColor redColor] CGColor];
{
CGRect r = CGRectMake(self.view.center.x, self.view.center.y, 0, 0);
r = CGRectInset(r, -22, -22);
outlineLayer.frame = r;
fillLayer.frame = r;
}
[self.view.layer addSublayer:fillLayer];
[self.view.layer addSublayer:outlineLayer];
}
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[self.view addGestureRecognizer:gr];
// WARNING - will create an ARC cycle. I don't care though, throwaway code
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(refreshDisplay:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
_displayLink.paused = true;
}
-(void) refreshDisplay:(CADisplayLink *) displayLink {
[CATransaction begin];
[CATransaction setDisableActions:true];
bool keepGoing = false;
for (int i=0; i<NUMLAYERS; ++i) {
CALayer *animatedLayer = [self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex];
if (animatedLayer.animationKeys.count) {
keepGoing = true;
}
CALayer *trackingLayer = [self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex+1];
trackingLayer.position = [animatedLayer.presentationLayer position];
}
_displayLink.paused = !keepGoing;
[CATransaction commit];
}
CGFloat randInRange(CGFloat r) {
return (rand()%((int)r*2))/2.0;
}
-(void) onTap:(UITapGestureRecognizer *) gr {
_displayLink.paused = false;
[CATransaction begin];
[CATransaction setAnimationDuration:2.0];
//[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
for (int i=0; i<NUMLAYERS; ++i) {
CGSize sz = self.view.frame.size;
CGPoint p = CGPointMake(randInRange(sz.width), randInRange(sz.height));
[[self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex] setPosition:p];
}
[CATransaction commit];
}
@end
我尝试了 3 种不同的计时功能,我看到了相同的结果。我还检查了带有仪器的帧速率,我得到了 58-60 fps 并且仍然看到可见的分离。
我想知道是否有人知道更好的方法来做到这一点。我知道我可以将所有动画转换为由 CAAnimation 或 CADisplayLink 驱动。但是,就我而言,两者都没有吸引力。我在屏幕上有数百个图层,所有图层都是相互连接的(构成一个始终响应且完全动画的视图) - 有时是分层的。Core Animation 让生活变得更易于管理(我相信更有效率,尽管这可能是我滞后的根本原因是“效率”)——所以我不想把它扔掉。我对必须参数化我的所有动画并不感到兴奋。有许多层依赖于许多其他层的帧。能够为大部分工作运行动画,然后只是“手动”做一些极端情况
此外,我意识到 UIKit Dynamics 的构建是为了为动画和响应式视图提供良好的基础。但是,我正在调整图层的大小,并且屏幕上图层的位置需要约束和附件。将其扩展到数百甚至数千层似乎是有问题的,尤其是在 CPU 已经受到限制的情况下。