【问题标题】:Pre-rendered Core Graphics animation doesn't animate smoothly & hogs memory预渲染的 Core Graphics 动画动画不流畅并占用内存
【发布时间】:2015-12-11 19:27:46
【问题描述】:

我发布这个问题是为了回答the answers 中的一个问题:Multiple CALayer masks causing performance issues

所以,现在我尝试采用预渲染动画方法,我仍然无法获得流畅的动画。不仅如此,当在实际设备上运行时,应用程序由于内存问题会定期崩溃。

你可以在这里看到运行的动画:http://cl.ly/e3Qu(从视频上看可能没有那么糟糕,但集中在动画的边缘,在实际设备上表现更差。)

这是我的代码:

static CGFloat const animationDuration = 1.5;
static CGFloat const calculationRate = (1.0/40.0); // 40fps max.
static CGFloat const calculationCycles = animationDuration/calculationRate;


@implementation splashView {

    CADisplayLink* l;

    CGImageRef backgroundImg;

    UIColor* color;

    NSMutableArray* animationImages;

    NSTimeInterval currentTime;
}

-(void) beginAnimating {
    static dispatch_once_t d;
    dispatch_once(&d, ^{

        CGFloat totalDistance = 0;
        CGFloat screenProgress = 0;
        CGFloat deltaScreenProgress = 0;


        totalDistance = screenHeight()+screenWidth();

        color = [[lzyColors colors] randomColor];

        backgroundImg = textBG(color, screenSize()).CGImage;

        animationImages = [NSMutableArray array];

        NSLog(@"start");

        UIGraphicsBeginImageContextWithOptions(screenSize(), YES, 0);

        CGContextRef c = UIGraphicsGetCurrentContext();

        for (int i = 0; i <= (calculationCycles+1); i++) {

            UIImage* img = lzyCGImageFromDrawing(^{

                CGFloat height = screenHeight();
                CGFloat width = screenWidth();

                CGMutablePathRef p = CGPathCreateMutable();

                CGPoint startingPoint = [self pointBForProgress:screenProgress];

                CGPathMoveToPoint(p, nil, startingPoint.x, startingPoint.y);
                lzyCGPathAddLineToPath(p, [self pointAForProgress:screenProgress]);
                if ((width < screenProgress) && (screenProgress-deltaScreenProgress) < width) {
                    lzyCGPathAddLineToPath(p, (CGPoint){width, 0});
                }
                if (deltaScreenProgress != 0) lzyCGPathAddLineToPath(p, [self pointAForProgress:screenProgress-deltaScreenProgress-1]);
                if (deltaScreenProgress != 0) lzyCGPathAddLineToPath(p, [self pointBForProgress:screenProgress-deltaScreenProgress-1]);
                if ((height < screenProgress) && (screenProgress-deltaScreenProgress) < height) {
                    lzyCGPathAddLineToPath(p, (CGPoint){0, height});
                }
                CGPathCloseSubpath(p);

                CGContextAddPath(c, p);
                CGContextClip(c);
                CGPathRelease(p);

                CGContextSetFillColorWithColor(c, color.CGColor);
                CGContextFillRect(c, self.bounds);


                CGContextDrawImage(c, self.bounds, backgroundImg);

            });


            [animationImages addObject:img];

            deltaScreenProgress = screenProgress;
            screenProgress = (i*totalDistance)/calculationCycles;
            deltaScreenProgress = screenProgress-deltaScreenProgress;
        }


        NSLog(@"stop");


        currentTime = 0;

        l = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidFire)];
        [l addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

    });


}

-(void) displayLinkDidFire {

    NSTimeInterval deltaTime = l.duration;
    currentTime += deltaTime;

    if (currentTime <= animationDuration) {

        CGFloat prg = (currentTime/animationDuration);
        NSInteger image = roundf(([animationImages count]-1)*prg);

        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.layer.contents = (__bridge id _Nullable)(((UIImage*)[animationImages objectAtIndex:image]).CGImage);
        [CATransaction commit];
    } else {

        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.layer.contents = (__bridge id _Nullable)(((UIImage*)[animationImages lastObject]).CGImage);
        [CATransaction commit];
        [l invalidate];
        animationImages = nil;
    }

}


-(CGPoint) pointAForProgress:(CGFloat)progressVar {
    CGFloat width = screenWidth();
    return (CGPoint){(progressVar<width)?progressVar:width+1, (progressVar>width)?progressVar-width:-1};
}

-(CGPoint) pointBForProgress:(CGFloat)progressVar {
    CGFloat height = screenHeight();
    return (CGPoint){(progressVar>height)?(progressVar-height):-1, (progressVar<height)?progressVar:height+1};
}

@end

textBG() 函数只是做一些相当简单的核心图形绘制来获取背景图像。

我只能假设我在这里做了一些根本错误的事情,但我想不出它是什么。

关于如何提高性能和减少内存消耗(不降低动画质量)的任何建议?

【问题讨论】:

    标签: ios objective-c performance core-graphics core-animation


    【解决方案1】:

    通过图层内容对全屏图像进行动画处理肯定会出现性能和内存问题,尤其是在@3x 设备上。对于您在另一个问题 (this video) 中显示的动画,看起来您实际上根本不需要任何遮罩 - 创建一系列矩形纯色图层(黑色、浅紫色、中紫色、深色紫色),将它们从前到后分层(文本层位于浅色和中色之间),将它们旋转到您需要的角度,并根据需要移动它们。

    如果您最终需要一个更复杂的动画,而该方法不适用于(或者一般而言,用于为任意全屏内容制作动画),您需要 (1) 将其预渲染为视频 (离线或使用 AVFoundation API)并以这种方式播放或 (2) 使用 OpenGL 或 Metal 进行绘图。

    【讨论】:

    • 问题是,可能很难从视频中分辨出来,但每一层都有渐变。因此,移动图层本身会产生不良影响。我想我只是要飞跃到 openGL。
    【解决方案2】:

    你遇到的问题是动画逻辑写得不好,它基本上是在内存中分配一大堆图像,这种方式肯定会让你的设备早晚崩溃。您需要使用一种更好的方法重新开始,该方法不会同时将所有图像数据拉入内存。不要只是试图调整你已经拥有的东西,因为以前的开发人员所做的基本假设是完全错误的。有关“video-and-memory-usage-on-ios-devices”等一些好的链接,请参阅我之前的示例到类似问题:https://stackoverflow.com/a/17224710/763355

    【讨论】:

    • 请注意,还有一个非常简化的 impl(代码现在有点旧,但基本方法仍然有效):stackoverflow.com/a/4650537/763355
    • 我仍然不满意这会在应用打开时造成的延迟,即使我之后将动画保存到磁盘,也必须至少执行 15 次才能解决我计划使用的不同颜色。感谢您的帮助,但就像我在对 Noah 的回答的评论中所说的那样,我想我只是要跳入 openGL,这应该有助于为我提供一个更通用的解决方案,在更复杂的动画中表现更好。
    • 好吧,您的实施完全取决于您。请记住,您可能没有考虑所有的可能性,并且从已经有效的东西开始可以节省您数周的精力。例如,您可能想看看这篇博文:modejong.com/blog/post11_opengl_color_cycle/index.html
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-30
    • 2010-11-07
    • 2017-07-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-15
    • 1970-01-01
    相关资源
    最近更新 更多