3

我正在尝试将 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 已经受到限制的情况下。

4

0 回答 0