关于Core Animation的一些初步探索

所谓Core Animation,顾名思义就是用来做动画的,它包含了一些Objective-C类,这些类都在Quartz Core框架中。
用它的原因也无需多说,首先是性能很好,使用了GPU硬件加速;其次是接口易用,毕竟是Objective-C,不需要像OpenGL ES一样完全和C打交道。
不过要掌握它也很费劲,这2天就遇到了不少问题,于是记录在此。

首先说下起因吧,其实我主要是想模拟UITableViewCell中,对imageView赋值时的动画效果。即从nil更改为一个UIImage对象后,图像的大小会从0逐渐放大,而右侧的textLabel和detailTextLabel也会右移。
这个默认的实现应该是用UIView做的,先调用UIView的beginAnimations:context:方法,然后修改imageView、textLabel和detailTextLabel的frame,再调用UIView的commitAnimations方法,就会自动使用动画来表现frame的更改了。
不过使用UIView也就意味着绘制性能的下降,造成滚动时不太流畅,于是我想到了轻量级的CALayer。

CALayer就是一个层。和绘图软件中的图层一样,CALayer也可以叠加,最终混合成一个可见的视图。
实际上UIView对象就有个layer属性,那就是它的根层,添加到这个层上面的CALayer对象就会随这个UIView一起绘制出来了。
CALayer *layer = [[CALayer alloc] init];
layer.frame = imageRect;
layer.contents = (id)image.CGImage;
cell.imageLayer = layer;
[cell.contentView.layer addSublayer:layer];
[layer release];
上述代码中,把layer.contents赋值为一个CGImageRef对象后,就会在这个layer中显示这张图像了。

层还可以实现一些特效,例如圆角和阴影:
layer.cornerRadius = 10.0;
layer.masksToBounds = YES;
这段代码就给层加上了10像素半径的圆角,不过你会发现绘制性能显著下降了,所以如果不是必须的话,可以先 在CGContextRef中画出圆角图像,再绘制到layer或view中。不过原文中没有设置scale,在iPhone 4下效果很差,因此最后创建时要改成[UIImage imageWithCGImage:imageMasked scale:[[UIScreen mainScreen] scale] orientation:UIImageOrientationUp]。

而如果要显示文字的话,可以继承CALayer,然后改写drawInContext:方法;或者实现delegate的drawLayer:inContext方法。代码可见 Add text to CALayer

不过当时我用的是CATextLayer这个子类,用法也很简单:
cell.textLayer = [CATextLayer layer];
textLayer.frame = textRect;
textLayer.truncationMode = kCATruncationEnd;
textLayer.foregroundColor = black;
textLayer.backgroundColor = white;
textLayer.wrapped = YES;
textLayer.fontSize = 17;
textLayer.string = text;
[cell.layer addSublayer:textLayer];
然而用了以后,我感觉被Apple坑了…

首先是啥都没看到,查了下文档,发现默认的字体颜色是白色,于是手动设置为黑色:
textLayer.foregroundColor = [UIColor blackColor].CGColor;
接着是文字过长时,会直接切断文字,而不显示省略号,并且有些文字可能只显示一半。如果把textLayer.wrapped设为NO,确实可以让它以省略号的形式截断,但是就只能显示一行了…貌似无解,除非自行计算长度并截断。
然后是在iPhone 4上测试时,发现文字没有抗锯齿。搜索了一下,发现需要手动设置contentsScale:
textLayer.contentsScale = [[UIScreen mainScreen] scale];
还有在滚动时,因为table cell是重用的,更改文字会出现短暂的残像。最后发现更改contents属性也会产生动画,因此需要去掉这个动画效果:
NSDictionary *actions = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"contents", nil];
textLayer.actions = actions;
[newActions release];
此外,采用相同字体和字号的时候,CATextLayer里的文本会比直接用NSString的drawInRect:withFont:lineBreakMode:方法粗一些,原因不明。
另外,我还发现CALayer默认是透明的,改成不透明后就显示成黑色了,而且改背景色也没用,原因亦不明。
于是最终我去掉了textLayer,而是直接调用NSString的drawInRect:withFont:lineBreakMode:方法。由于动画时间很短,倒也不影响视觉效果。

创建CALayer搞定了,接下来就该实现动画了。和UIView一样,修改CALayer的属性就会自动生成动画了。因此初始时可以把imageLayer的bounds的长宽都设为0,然后赋值为一个正数,它就会自动放大了。
不过默认的动画时间很短,如果要修改的话,就需要用到CATransaction了:
[CATransaction begin];
[CATransaction setAnimationDuration:1];
imageLayer.frame = imageRect;
[CATransaction commit];
CATransaction还有其他的作用,最重要的功能就是让其中的所有动画在commit时并行执行,以使它们同步。这样如果同时放大图像并缩小文字的话,就不会出现图像比文字先变化,而覆盖文字的情况。
此外还可以用下述语句来禁用一些动画,不过并非万能:
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

当然也可以自定义动画,例如使用CABasicAnimation:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.duration = 1;
CGPoint point = {100, 0};
animation.byValue=[NSValue valueWithCGPoint:point];
layer.position = position;
[layer addAnimation:animation forKey:@"positionAnimation"];
这段代码就让layer右移了100 point。不过执行完后,你会发现它又立刻回到原处了。

实际上Core Animation为layer维护了3种tree:
  1. 第一种是我们可以直接访问的layer tree,对CALayer的属性赋值后,就会立刻更改。
  2. 第二种是presentation tree,它存储了动画进行过程中的属性。因此在整个动画过程中,它会不断变化。我们可以通过它来得知layer当前的显示状态。
  3. 第三种是render tree,它根据presentation tree来计算,我们不能访问它。
知道这个后就清楚原因了。上例中我们执行了一个动画,它的presentation tree一直在变化,直到动画结束。接着它发现layer tree和presentation tree不一致,于是又自动更新了layer的位置,所以就还原了。
因此在执行完动画后,立刻更改CALayer的属性,应该就不会自动还原了,不过我懒得去试了。

现在就来实现一下drawRect:方法:
- (void)drawRect:(CGRect)rect {
    [CATransaction begin];
    if (shouldAnimatied) {
        [CATransaction setAnimationDuration:.3];
    } else {
        [CATransaction setAnimationDuration:0];
        [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    }

    if (image) {
        if (imageLayer.hidden) {
            imageLayer.hidden = NO;
            imageLayer.frame = imageRect;
        }
        imageLayer.contents = (id)image.CGImage;
        self.image = nil;
        [textString drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation];
    } else {
        imageLayer.hidden = YES;
        imageLayer.frame = imageRect0;
        [textString drawInRect:textRect0 withFont:font lineBreakMode:UILineBreakModeTailTruncation];
    }
    [CATransaction commit];
    shouldAnimatied = NO;
}
其中shouldAnimatied是在drawRect:前设置的,只有当前cell可见,并且图像下载完时,才需要用动画的方式显示出来。
当没有图像时,就直接显示文字,并将imageLayer隐藏,长宽设为0;而有图像时,就将其显示出来,长宽增大,而文本则画在右侧。
目前这个版本在图像全部载入过的情况下,滚动时能稳定在55fps以上,但显示新图像时会降低到低于30fps,并明显感到很卡。而直接调用[image drawAtPoint:imagePoint]的话,无论何时基本都能维持在55fps以上。

所以考虑了另一个方案,即平时隐藏imageLayer,通过调用drawAtPoint:来显示图像。只有在动画更新时才显示imageLayer。
- (void)drawRect:(CGRect)rect {
    if (shouldAnimatied) {
        [CATransaction begin];
        [CATransaction setAnimationDuration:.3];
        imageLayer.hidden = NO;
        imageLayer.frame = imageRect;
        imageLayer.contents = (id)image.CGImage;
        [CATransaction commit];
        self.image = nil;
        [textString drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation];
        shouldAnimatied = NO;
    } else {
        if (!imageLayer.hidden) {
            [CATransaction begin];
            [CATransaction setAnimationDuration:0];
            [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
            imageLayer.hidden = YES;
            imageLayer.frame = imageRect0;
            [CATransaction commit];
        }
        if (image) {
            [image drawAtPoint:imagePoint];
            self.image = nil;
            [textString drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation];
        } else {
            [textString drawInRect:textRect0 withFont:font lineBreakMode:UILineBreakModeTailTruncation];
        }
    }
}
现在这个版本也基本上稳定到55fps以上了,总算大功告成了~
主要内容:本文详细介绍了一种QRBiLSTM(分位数回归双向长短期记忆网络)的时间序列区间预测方法。首先介绍了项目背景以及模型的优势,比如能够有效利用双向的信息,并对未来的趋势上限和下限做出估计。接着从数据生成出发讲述了具体的代码操作过程:数据预处理,搭建模型,进行训练,并最终可视化预测结果与计算分位数回归的边界线。提供的示例代码可以完全运行并且包含了数据生成环节,便于新手快速上手,深入学习。此外还指出了模型未来发展的方向,例如加入额外的输入特性和改善超参数配置等途径提高模型的表现。文中强调了时间序列的标准化和平稳检验,在样本划分阶段需要按时间序列顺序进行划分,并在训练阶段采取合适的手段预防过度拟合发生。 适合人群:对于希望学习和应用双向长短时记忆网络解决时序数据预测的初学者和具有一定基础的研究人员。尤其适用于有金融数据分析需求、需要做多一步或多步预测任务的从业者。 使用场景及目标:应用于金融市场波动预报、天气状况变化预测或是物流管理等多个领域内的决策支持。主要目的在于不仅能够提供精确的数值预计还能描绘出相应的区间概率图以增强结论置信程度。 补充说明:本教程通过一个由正弦信号加白噪构造而成的简单实例来指导大家理解和执行QRBiLSTM流程的所有关键步骤,这既方便于初学者跟踪学习,又有利于专业人士作为现有系统的补充参考工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值