[译] Core Animation 编程指南 - 高级动画技巧

本文首发地址html

有不少方法去配置基于属性或者关键帧的动画,来为你作更多的事情。Apps 须要同时或顺序执行多个动画可使用高级行为去同步多个动画的时间或将它们串在一块儿。你可使用动画对象的其余类型去建立视觉形变和其它有意思的动画效果。git

过渡动画支持更改图层可见性

如上述标题所示,过渡动画对象会为图层建立视觉过渡动画效果。过渡对象最多见的用法就是动画的显示一个图层,隐藏另外一个图层。不像基于属性的动画,动画图层的某个属性,过渡动画操做图层的缓存图像去建立视觉效果,这个仅经过改变属性是很难或者不可能实现的。过渡的标准类型容许你执行显示、推进、移动或交叉渐变更画。在 OS X上,你也可使用 Core Image 过滤器去建立过渡动画,可实现如擦拭、页面卷曲、波纹或自定义效果。github

执行过渡动画,你须要建立一个 CATransition 对象,并将它添加到涉及过渡动画的图层上。你使用过渡对象去指定须要执行的过渡类型,即动画的起点和终点。你也不须要整个过渡动画。在动画期间,过渡对象容许你指定开始和结束的过程值。这些值容许你在动画的中点开始或者结束动画。编程

例 5-1 展现了实现两个视图之间建立一个推进过渡动画的代码。在该例中,myView1myView2 在同一父视图的同一个位置,但仅 myView1 可见。推进过渡动画会致使 myView1 向左边滑动消失,同时 myView2 从右向左滑动显示。更新两个视图的 hidden 属性确保在动画结束时两个视图的可见性是正确的。缓存

例 5-1 在 iOS 上给两个视图添加过渡动画bash

CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
 
// 给两个图层添加过渡动画
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
 
// 最后,改变图层的可见性
myView1.hidden = YES;
myView2.hidden = NO;

// Swift 
let transition = CATransition()
transition.startProgress = 0.0
transition.endProgress = 1.0
transition.type = .push
transition.subtype = .fromRight
transition.duration = 3.0

myView1.layer.add(transition, forKey: "transition")
myView2.layer.add(transition, forKey: "transition")

myView1.isHidden = true
myView2.isHidden = false
复制代码

下方为译者补全的 例 5-1 测试代码:app

class ViewController: UIViewController {
    var myView1: UIView!
    var myView2: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let transition = CATransition()
        transition.startProgress = 0.0
        transition.endProgress = 1.0
        transition.type = .push
        transition.subtype = .fromRight
        transition.duration = 3.0

        myView1.layer.add(transition, forKey: "transition")
        myView2.layer.add(transition, forKey: "transition")

        myView1.isHidden = true
        myView2.isHidden = false
    }
}

extension ViewController {
    func setupSubviews() {
        myView2 = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
        myView2.backgroundColor = UIColor.blue
        view.addSubview(myView2)
        
        myView1 = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
        myView1.backgroundColor = UIColor.red
        view.addSubview(myView1)
    }
}
复制代码

当一个过渡包含两个图层时,你能够对两个图层使用同一个过渡对象。使用同一个过渡对象能够简化你的代码。可是,若是每一个图层的过渡参数是不一样的,你就必须对它们使用不一样的过渡对象了。ide

例 5-2 展现了如何使用 Core Image 过滤器在 OS X 上实现过渡效果。在用你须要的参数配置完过滤器后,将它赋值给过渡对象的 filter 属性。在这以后,应用动画的过程就和其它类型动画对象一致了。函数

例 5-2 使用 Core Image 过滤器在 OS X 上实现过渡动画测试

// Create the Core Image filter, setting several key parameters.
CIFilter* aFilter = [CIFilter filterWithName:@"CIBarsSwipeTransition"];
[aFilter setValue:[NSNumber numberWithFloat:3.14] forKey:@"inputAngle"];
[aFilter setValue:[NSNumber numberWithFloat:30.0] forKey:@"inputWidth"];
[aFilter setValue:[NSNumber numberWithFloat:10.0] forKey:@"inputBarOffset"];
 
// Create the transition object
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.filter = aFilter;
transition.duration = 1.0;
 
[self.imageView2 setHidden:NO];
[self.imageView.layer addAnimation:transition forKey:@"transition"];
[self.imageView2.layer addAnimation:transition forKey:@"transition"];
[self.imageView setHidden:YES];
复制代码

注意:当在动画中使用 Core Image 过滤器时,最棘手的部分就是配置过滤器。例如,使用条形滑动过渡,若是输入角度太高或者太低均可能会致使过渡效果没有发生同样。若是你没有看见你期待的动画效果,能够调整过滤器的参数来看一下结果是否会发生变化。

自定义动画的时间

时间是动画重要的一部分,使用 Core Animation ,你能够经过 CAMediaTiming 协议的方法和属性给你的动画指定的精确时间信息。两个 Core Animation 类遵照该协议。CAAnimation 类遵照它,因此你能够给你的动画对象指定时间信息。CALayer 遵照它,因此你能够给你的隐式动画配置一些事件相关的特性,即便隐式过渡对象包含的动画一般提供优先的默认时间信息。

当讨论时间和动画时,理解图层对象是如何与时间协做是很是重要的。每一个图层有它们本身的本地时间,来管理动画时间。一般,两个不一样图层的本地时间足够接近,你能够给两个图层指定相同的时间值,用户一般不会察觉。可是,图层的本地时间能够被它的父图层修改,或者它本身的时间参数。例如,改变图层的 speed 属性会致使图层(包括它的子图层)上的动画持续时间按比例变化。

为协助你确保给定图层的时间值是合适的,CALayer 类定义了 convertTime:fromLayer:convertTime:toLayer: 方法。你可使用这些方法将固定时间转为图层的本地时间,或将时间值从一个图层转到另外一个图层。这些方法考虑了可能影响层的本地时间的媒体定时属性,并返回一个能够与另外一层一块儿使用的值。在例 5-3 中,展现了如何使用常规方法获取图层的本地时间。CACurrentMediaTime() 是一个返回计算机当前时钟时间的便利函数,该方法获取时钟时间并将它转为图层的本地时间。

例 5-3 得到图层的当前本地时间

CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
复制代码

一旦你得到了图层的本地时间值,你可使用该值去更新动画对象或图层的时间相关属性。经过这些时间属性,你能够实现一些有意思的动画行为,包含:

  • 使用 beginTime 属性设置动画的开始时间。一般,动画在下一个更新循环期间开始。你可使用 beginTime 参数将动画的开始时间延迟几秒钟。将两个动画串在一块儿的方式是:将一个动画的开始时间设置为另外一个动画的结束时间。若是你延迟了动画的开始,你可能还想把 fillMode 属性值设置为 kCAFillModeBackwards 。该填充模式会形成图层显示动画的开始值,即便图层树中的图层对象包含不一样的值。不用该填充模式,你将会看到在动画执行以前调到结束值。其余的模式也是有效的。
  • autoreverses 属性会使动画在指按期间内执行,而后返回到动画初始值状态。你能够将该属性和 repeatCount 属性联合使用,在开始值和结束值之间来回动画显示。将自动复制动画的重复计数设置为整数(如1.0),会致使动画在其起始值中止。添加额外的半步(例如重复计数为1.5)会致使动画在其结束值中止。
  • 组动画使用 timeOffset 属性能够在比其它动画更晚的时间开始一些动画。

暂停、恢复动画

为了暂停动画,你能够利用图层遵照 CAMediaTiming 协议的事实,将图层动画的速率设置为 0.0。将速率改成非零值即恢复动画。例 5-4 展现了如何暂停恢复图层动画的代码实现。

例 5-4 暂停重启图层的动画

- (void)pauseLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
   layer.speed = 0.0;
   layer.timeOffset = pausedTime;
}
 
- (void)resumeLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer timeOffset];
   layer.speed = 1.0;
   layer.timeOffset = 0.0;
   layer.beginTime = 0.0;
   CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
   layer.beginTime = timeSincePause;
}
复制代码

显式事务容许你改变更画参数

你作的每一个图层的改变必须是事务的一部分。CATransaction 类管理动画的建立和组织,让它们在合适的时间执行。在多数状况下,你不须要建立本身的事务。不管什么时候你给图层添加隐式或显示动画,Core Animation 都会自动建立隐式事务。然而,你也能够建立显式事务来更精确的管理动画。

你使用 CATransaction 类的方法去建立管理事务。调用 begin 类方法去开始新的事务;调用 commit 类方法去终止事务。在这两个方法之间是你想要的事务改变的部分。例如,改变图层的两个属性,你可使用例 5-5 的代码。

例 5-5 建立显式事务

[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
复制代码

使用事务的一个主要缘由是,在显式事务范围内,你能够修改持续时间,时间函数,和其它的参数。你也能够赋值一个 completion block 去获取事务,当组动画完成的时候,使你的 app 能够被通知。使用 setValue:forKey: 方法和事务字典包含的键来改变响应的动画参数。例如,将默认的持续时间改成 10 秒,你将改变 kCATransactionAnimationDuration 键的值,如例 5-6 所示。

例 5-6 改变更画默认的持续时间

[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
                 forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];
复制代码

在你想给集合中不一样动画设置不一样值得时候,你能够嵌入事务。将事务嵌入到另外一个事务,只需再调一次 begin 类方法便可。每一次 begin 方法的调用都要有相匹配的 commit 方法的调用。只有在提交最外层事务的更改后,Core Animation 才会开始相关的动画。

例 5-7 展现了将一个事务嵌入到另外一个事务的例子。在该例中,内部事务更改与外部事务相同的动画参数,但使用不一样的值。

例 5-7 嵌套显式事务

[CATransaction begin]; // Outer transaction
 
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
                forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);
 
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
                 forKey:kCATransactionAnimationDuration];
 
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
 
[CATransaction commit]; // Inner transaction
 
[CATransaction commit]; // Outer transaction
复制代码

给动画添加透视效果

Apps 能够在三维空间中操做图层,可是为了简单起见,Core Animation 使用平行投影显示图层,这基本上将场景展平为二维平面。这种默认行为会致使具备不一样 zPosition 值的大小相同的图层显示为相同的大小,即便它们在z轴上相距很远。你一般会看到三维场景的点已经消失了。可是,你能够经过修改图层的转换矩阵以包含透视信息来更改该行为。

修改场景透视图时,须要修改包含正在查看的图层的最高层的 sublayerTransform 矩阵。经过将相同的透视信息应用于全部子层,修改最高级简化了你必须写的代码。它还确保透视正确应用于在不一样平面中彼此重叠的同级子图层。

例 5-8 展现了为父图层建立简单透视变换的方法。在这种状况下,eyePosition 变量指定沿 z 轴查看图层的相对距离。一般,你为 eyePosition 指定一个正值,以保持图层以预期的方式定向。值越大,场景越平坦,而值越小,图层间的视觉差别越大。

例 5-8 给父图层添加透视形变

CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;
 
// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;
复制代码

经过父图层的配置,你能够改变任何子图层的 zPosition 属性,观察它们的大小如何基于它们与眼的相对距离而变化的。

最后

相关文章
相关标签/搜索