将CADisplayLink更改同步到正在进行的CAAnimation

时间:2015-03-03 16:05:34

标签: ios core-animation caanimation cadisplaylink

我尝试将CADisplayLink和CAAnimation结合起来,以便我可以使用CAAnimation为某些图层设置动画,并根据动画图层的更改表示层实时调整其他图层。我意识到CAAnimations正在我的进程之外被驱动,而CADisplayLink回调在我的进程中,所以同步永远不会完全紧张。但是,我希望它会比我看到的更好(我看到可能会达到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驱动。但是,就我而言,两者都没有吸引力。我在屏幕上有数百个图层,所有图层都互相连接(构成一个始终响应和完全动画的视图) - 有时是分层次的。核心动画让生活变得更易于管理(而且我认为效率更高,尽管可能是效率&#34;这是我滞后的根本原因) - 所以我不想抛出那个。我不必为我的所有动画参数化而感到兴奋。有许多层依赖于许多其他层的帧。能够为大部分工作运行动画,然后只是做一些角落案例&#34;手工&#34;从设计角度(尤其是维护方面),实时非常吸引人。

另外,我意识到UIKit Dynamics的构建是为动画和响应式视图提供良好的基础。但是,我调整了图层的大小,屏幕上各层的位置需要约束和附件。将其扩展到数百甚至数千个层似乎存在问题,尤其是当CPU已经受到限制时。

0 个答案:

没有答案