22

私はObjective-C/iOSプログラミングに不慣れで、UIViewアニメーションが内部でどのように機能するかを理解しようとしています。

次のようなコードがあるとします。

[UIView animateWithDuration:2.0 animations:^{
    self.label.alpha = 1.0;
}];

引数として渡されるのanimationsは、実行可能なObjective-Cブロック(他の言語のラムダ/無名関数のようなもの)であり、その後、のalphaプロパティをlabel現在の値から1.0に変更します。

ただし、ブロックはアニメーションの進行状況の引数を受け入れません(たとえば、0.0から1.0または0から1000になります)。私の質問は、ブロックが最終状態のみを指定するため、アニメーションフレームワークがこのブロックを使用して中間フレームについて知る方法です。

編集:私の質問は、メソッドの使用方法ではなく、メソッドの内部操作に関するものです。animateWithDuration

コードがどのように機能するかについての私の仮説はanimateWithDuration次のとおりです。

  1. animateWithDuration変更が実際には実行されず、登録されるだけのすべてのビューオブジェクトに対して、ある種の特別な状態をアクティブにします。
  2. ブロックを実行し、変更を登録します。
  3. ビューオブジェクトに変更された状態を照会し、初期値とターゲット値を取得するため、変更するプロパティと変更を実行する範囲を認識します。
  4. 継続時間と初期/ターゲット値に基づいて中間フレームを計算し、アニメーションを起動します。

誰かがanimateWithDuration本当にそのように機能するかどうかに光を当てることができますか?

4

3 に答える 3

24

もちろん、UIKitはオープンソースではなく、Appleで働いていないため、内部で何が起こっているのか正確にはわかりませんが、いくつかのアイデアがあります。

ブロックベースのUIViewアニメーションメソッドが導入される前は、アニメーションビューは次のように見えましたが、これらのメソッドは実際には引き続き使用できます。

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
myView.center = CGPointMake(300, 300);
[UIView commitAnimations];

これを知っていると、次のような独自のブロックベースのアニメーションメソッドを実装できます。

+ (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:duration];
    animations();
    [UIView commitAnimations];
}

animateWithDuration:animations:...これは既存の方法とまったく同じように機能します。

UIViewブロックを方程式から外すと、アニメーションブロック内で行われたときに、その(アニメーション化可能な)プロパティへの変更をアニメーション化するために使用する、ある種のグローバルアニメーション状態が必要であることが明らかになります。ネストされたアニメーションブロックを持つことができるため、これはある種のスタックである必要があります。

実際のアニメーションは、レイヤーレベルで機能するCore Animationによって実行されます。各アニメーションには、アニメーションと合成を担当UIViewするバッキングインスタンスがありますが、ビューは、ほとんどの場合、タッチイベントと座標系の変換を処理します。CALayer

ここでは、Core Animationの仕組みについては詳しく説明しません。そのために、 CoreAnimationプログラミングガイドを読むことをお勧めします。基本的に、これは、すべてのキーフレームを明示的に計算せずに、レイヤーツリーの変更をアニメーション化するシステムです(コアアニメーションから中間値を取得することは実際にはかなり困難です。通常、値、期間などを指定して、システムに許可します。詳細に注意してください)。

UIViewに基づいているためCALayer、そのプロパティの多くは実際には基礎となるレイヤーに実装されています。たとえば、を設定または取得する場合view.center、これはと同じでview.layer.locationあり、これらのいずれかを変更すると、もう一方も変更されます。

レイヤーは明示的にアニメーション化できます(これは、単純なものやより複雑なものなどCAAnimation、いくつかの具体的な実装を持つ抽象クラスです)。CABasicAnimationCAKeyframeAnimation

UIViewでは、アニメーションブロック内で「魔法のように」アニメーション化する変更を実現するために、プロパティセッターは何を行うことができるでしょうか。それらの1つを再実装できるかどうかを見てみましょう。簡単にするために、を使用してみましょうsetCenter:

my_animateWithDuration:animations:まず、グローバルを使用する上記のメソッドの修正バージョンを次に示します。これにより、アニメーションにかかる時間をメソッドCATransactionで確認できます。setCenter:

- (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    animations();

    [CATransaction commit];
}

beginAnimations:...を使用しなくなったことに注意してくださいcommitAnimations。他に何もしなければ、何もアニメーション化されません。

それでは、サブクラスでオーバーライドsetCenter:しましょう。UIView

@interface MyView : UIView
@end

@implementation MyView
- (void)setCenter:(CGPoint)position
{
    if ([CATransaction animationDuration] > 0) {
        CALayer *layer = self.layer;
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
        animation.fromValue = [layer valueForKey:@"position"];
        animation.toValue = [NSValue valueWithCGPoint:position];
        layer.position = position;
        [layer addAnimation:animation forKey:@"position"];
    }
}
@end

ここでは、基になるレイヤーのプロパティをアニメーション化するCoreAnimationを使用して明示的なアニメーションを設定します。locationアニメーションの長さは、から自動的に取得されますCATransaction。試してみましょう:

MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];

[self my_animateWithDuration:4.0 animations:^{
    NSLog(@"center before: %@", NSStringFromCGPoint(myView.center));
    myView.center = CGPointMake(300, 300);
    NSLog(@"center after : %@", NSStringFromCGPoint(myView.center));
}];

UIViewこれがまさにアニメーションシステムの仕組みであると言っているのではなく、原則としてどのように機能するかを示すためだけのものです。

于 2013-03-03T18:38:53.870 に答える
1

中間フレームの値は指定されていません。値のアニメーション(この場合はアルファ、色、位置など)は、以前に設定された値とアニメーションブロック内で設定された宛先値の間で自動的に生成されます。を使用してオプションを指定することにより、曲線に影響を与えることができますanimateWithDuration:delay:options:animations:completion:(デフォルトはUIViewAnimationOptionCurveEaseInOut、つまり、値の変更の速度が加速および減速します)。

以前に設定されたアニメーション化された値の変更が最初に終了することに注意してください。つまり、各アニメーションブロックは、前の終了値から新しい値への新しいアニメーションを指定します。UIViewAnimationOptionBeginFromCurrentStateすでに進行中のアニメーションの状態から新しいアニメーションを開始するように指定できます。

于 2013-03-02T15:58:52.093 に答える
0

これにより、ブロック内で指定されたプロパティが現在の値から指定した値に変更され、期間全体にわたって線形に変更されます。

したがって、元のアルファ値が0の場合、これは2秒でラベルにフェードインします。元の値がすでに1.0の場合、効果はまったく見られません。

内部でUIViewは、変更を行うために必要なアニメーションフレームの数を把握します。

イージングカーブをとして指定することにより、変更が行われるレートを変更することもできますUIViewAnimationOption。繰り返しUIViewますが、トゥイーンを処理します。

于 2013-03-02T16:01:25.093 に答える