ホームページ > 記事 > ウェブフロントエンド > [iOSアニメーション]CALayer-Layer Time 2_html/css_WEB-ITnose
コア アニメーションについて説明するたびに、時間は相対的なものであり、それぞれのアニメーションには独自の時間があり、個別に加速、遅延、オフセットすることができます。
beginTime はアニメーションが開始するまでの遅延時間を指定します。ここでの遅延は、アニメーションが表示レイヤーに追加された瞬間から測定され、デフォルトは 0 (アニメーションがすぐに実行されることを意味します) です。
speed は時間の倍数で、デフォルトは 1.0 で、これを減らすとレイヤー/アニメーションの時間が遅くなり、増やすと速度が上がります。速度が 2.0 の場合、持続時間 1 のアニメーションは実際には 0.5 秒で完了します。
timeOffset は beginTime に似ていますが、beginTime を増やすことによって生じるアニメーションの遅延とは異なり、timeOffset を増やすとアニメーションが特定の時点まで早送りされるだけです。たとえば、1 秒続くアニメーションの場合、timeOffset を 0.5 に設定すると、アニメーションが早送りされます。半分の位置から始めます。
beginTime とは異なり、timeOffset は速度の影響を受けません。したがって、speed を 2.0 に設定し、timeOffset を 0.5 に設定すると、1 秒のアニメーションが実際には 0.5 秒に短縮されるため、アニメーションは最後に終了した位置から開始されます。ただし、timeOffset を使用してアニメーションを終了位置から開始しても、アニメーションはループして最初から再生を開始するだけです。
リスト 9.3 のテスト プログラムで検証し、speed および timeOffset スライダーをランダムな値に設定し、[再生] をクリックして効果を観察できます (図 9.3 を参照)
リスト 9.3 timeOffset プロパティと Speed プロパティをテストします
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nonatomic, weak) IBOutlet UILabel *speedLabel;@property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;@property (nonatomic, weak) IBOutlet UISlider *speedSlider;@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;@property (nonatomic, strong) UIBezierPath *bezierPath;@property (nonatomic, strong) CALayer *shipLayer;@end@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; //create a path self.bezierPath = [[UIBezierPath alloc] init]; [self.bezierPath moveToPoint:CGPointMake(0, 150)]; [self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)]; //draw the path using a CAShapeLayer CAShapeLayer *pathLayer = [CAShapeLayer layer]; pathLayer.path = self.bezierPath.CGPath; pathLayer.fillColor = [UIColor clearColor].CGColor; pathLayer.strokeColor = [UIColor redColor].CGColor; pathLayer.lineWidth = 3.0f; [self.containerView.layer addSublayer:pathLayer]; //add the ship self.shipLayer = [CALayer layer]; self.shipLayer.frame = CGRectMake(0, 0, 64, 64); self.shipLayer.position = CGPointMake(0, 150); self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; [self.containerView.layer addSublayer:self.shipLayer]; //set initial values [self updateSliders];}- (IBAction)updateSliders{ CFTimeInterval timeOffset = self.timeOffsetSlider.value; self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset]; float speed = self.speedSlider.value; self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];}- (IBAction)play{ //create the keyframe animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; animation.timeOffset = self.timeOffsetSlider.value; animation.speed = self.speedSlider.value; animation.duration = 1.0; animation.path = self.bezierPath.CGPath; animation.rotationMode = kCAAnimationRotateAuto; animation.removedOnCompletion = NO; [self.shipLayer addAnimation:animation forKey:@"slide"];}@end
図 9.3 時間オフセットと速度をテストするための簡単なアプリケーション
beginTime が 0 以外のアニメーションの場合、アニメーションがレイヤーに追加されるときに何も起こらない状態になります。同様に、removeOnCompletion が NO に設定されているアニメーションは、アニメーションが終了しても以前の状態を維持します。ここで、アニメーションの開始前と終了後のアニメーション プロパティの値はどうなるのかという疑問が生じます。
可能性の 1 つは、プロパティとアニメーションが追加される前、つまりモデル レイヤーで定義された値が一貫していることです (第 7 章「暗黙的なアニメーション」、モデル レイヤーとプレゼンテーション レイヤーの説明を参照)。
もう 1 つの可能性は、アニメーションの開始前のフレーム、またはアニメーションの終了後のフレームを保持することです。アニメーションの開始値と終了値は開始前と終了後の時間をパディングするために使用されるため、これはパディングと呼ばれます。
この動作は開発者に任されており、CAMediaTiming の fillMode によって制御できます。 fillMode は、次の 4 つの定数を受け入れることができる NSString 型です:
kCAFillModeForwards kCAFillModeBackwards kCAFillModeBoth kCAFillModeRemoved
デフォルトは kCAFillModeRemoved です。アニメーションが再生されなくなった場合、残りの 3 つの型は、forward、backward、または です。つまり、アニメーションの開始前または終了後にアニメーションの開始時と終了時の値が残るように、アニメーションの状態を前後に埋めます。
これは、アニメーション終了時の急激な復帰を避けるための別の解決策を提供します (第 8 章を参照)。ただし、この問題を解決するためにこれを使用する場合は、removeOnCompletion を NO に設定する必要があり、アニメーションが不要になったときにレイヤーから削除できるように、空ではないキーをアニメーションに追加する必要があることに注意してください。
第 3 章「レイヤー ジオメトリ」では、各レイヤーがレイヤー ツリー内の親レイヤーを基準にして座標系を定義する方法を学習しました。アニメーション時間も同様に機能し、各アニメーションとレイヤーには、親を基準にして測定される独自の階層的な時間概念があります。レイヤの時間を調整すると、それ自体とサブレイヤのアニメーションに影響しますが、親レイヤには影響しません。もう 1 つの類似点は、すべてのアニメーションが (CAAnimationGroup インスタンスを使用して) 階層的にグループ化されていることです。
CALayer または CAGroupAnimation の継続時間とrepeatCount/repeatDurationプロパティを調整しても、子アニメーションには影響しません。ただし、beginTime、timeOffset、speed プロパティは子のアニメーションに影響します。ただし、階層関係では、beginTime は、親レイヤー (または合成関係の親アニメーション) がアニメーションを開始するときと、オブジェクトが独自のアニメーションを開始しようとするときとの間のオフセットを指定します。同様に、CALayer と CAGroupAnimation の速度プロパティを調整すると、アニメーションとサブアニメーションの速度にスケール係数が適用されます。
CoreAnimation には、いわゆるマッハ時間であるグローバル時間の概念があります (「マッハ」は実際には iOS および Mac OS システム カーネルの名前です)。マッハ時間はデバイス上のすべてのプロセスにグローバルですが、異なるデバイス間ではグローバルではありませんが、CACurrentMediaTime 関数を使用してマッハ時間にアクセスできます。
CFTimeInterval time = CACurrentMediaTime();
によって返される値。この関数は実際には無関係です (デバイスが最後に起動されてからの秒数を返しますが、実際の役割はアニメーションの時間測定の相対値を提供することです)。デバイスがスリープしているときはマッハ タイムが一時停止されることに注意してください。これは、すべての CAAnimation (マッハ タイムに基づく) も一時停止されることを意味します。
因此马赫时间对长时间测量并不有用。比如用CACurrentMediaTime去更新一个实时闹钟并不明智。(可以用[NSDate date]代替,就像第三章例子所示)。
每个CALayer和CAAnimation实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime,timeOffset和speed属性计算。就和转换不同图层之间坐标关系一样,CALayer同样也提供了方法来转换不同图层之间的本地时间。如下:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l; - (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
当用来同步不同图层之间有不同的speed,timeOffset和beginTime的动画,这些方法会很有用。
设置动画的speed属性为0可以暂停动画,但在动画被添加到图层之后不太可能再修改它了,所以不能对正在进行的动画使用这个属性。给图层添加一个CAAnimation实际上是给动画对象做了一个不可改变的拷贝,所以对原始动画对象属性的改变对真实的动画并没有作用。相反,直接用-animationForKey:来检索图层正在进行的动画可以返回正确的动画对象,但是修改它的属性将会抛出异常。
如果移除图层正在进行的动画,图层将会急速返回动画之前的状态。但如果在动画移除之前拷贝呈现图层到模型图层,动画将会看起来暂停在那里。但是不好的地方在于之后就不能再恢复动画了。
一个简单的方法是可以利用CAMediaTiming来暂停图层本身。如果把图层的speed设置成0,它会暂停任何添加到图层上的动画。类似的,设置speed大于1.0将会快进,设置成一个负值将会倒回动画。
通过增加主窗口图层的speed,可以暂停整个应用程序的动画。这对UI自动化提供了好处,我们可以加速所有的视图动画来进行自动化测试(注意对于在主窗口之外的视图并不会被影响,比如UIAlertview)。可以在app delegate设置如下进行验证:
self.window.layer.speed = 100;
你也可以通过这种方式来减速,但其实也可以在模拟器通过切换慢速动画来实现。
timeOffset一个很有用的功能在于你可以它可以让你手动控制动画进程,通过设置speed为0,可以禁用动画的自动播放,然后来使用timeOffset来来回显示动画序列。这可以使得运用手势来手动控制动画变得很简单。
举个简单的例子:还是之前关门的动画,修改代码来用手势控制动画。我们给视图添加一个UIPanGestureRecognizer,然后用timeOffset左右摇晃。
因为在动画添加到图层之后不能再做修改了,我们来通过调整layer的 timeOffset 达到同样的效果(清单9.4)。
清单9.4 通过触摸手势手动控制动画
@interface ViewController ()@property (nonatomic, weak) UIView *containerView;@property (nonatomic, strong) CALayer *doorLayer;@end@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; //add the door self.doorLayer = [CALayer layer]; self.doorLayer.frame = CGRectMake(0, 0, 128, 256); self.doorLayer.position = CGPointMake(150 - 64, 150); self.doorLayer.anchorPoint = CGPointMake(0, 0.5); self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage; [self.containerView.layer addSublayer:self.doorLayer]; //apply perspective transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective; //add pan gesture recognizer to handle swipes UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init]; [pan addTarget:self action:@selector(pan:)]; [self.view addGestureRecognizer:pan]; //pause all layer animations self.doorLayer.speed = 0.0; //apply swinging animation (which won't play because layer is paused) CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.toValue = @(-M_PI_2); animation.duration = 1.0; [self.doorLayer addAnimation:animation forKey:nil];}- (void)pan:(UIPanGestureRecognizer *)pan{ //get horizontal component of pan gesture CGFloat x = [pan translationInView:self.view].x; //convert from points to animation duration //using a reasonable scale factor x /= 200.0f; //update timeOffset and clamp result CFTimeInterval timeOffset = self.doorLayer.timeOffset; timeOffset = MIN(0.999, MAX(0.0, timeOffset - x)); self.doorLayer.timeOffset = timeOffset; //reset pan gesture [pan setTranslation:CGPointZero inView:self.view];}@end
这其实是个小诡计,也许相对于设置个动画然后每次显示一帧而言,用移动手势来直接设置门的transform会更简单。
在这个例子中的确是这样,但是对于比如说关键这这样更加复杂的情况,或者有多个图层的动画组,相对于实时计算每个图层的属性而言,这就显得方便的多了。
在这一章,我们了解了CAMediaTiming协议,以及Core Animation用来操作时间控制动画的机制。在下一章,我们将要接触缓冲,另一个用来使动画更加真实的操作时间的技术。