web-dev-qa-db-ja.com

モデルレイヤーを更新すると、CABasicAnimationが正しくアニメーション化されません

私は現在、CABasicAnimationCALayerプロパティをアニメーション化するtransformを実装しています。今、私はCore Animationを初めて使用しますが、objc.ioなどのさまざまなブログや記事を通じて、アニメーションを使用してアニメーションを固定するために、頻繁に(誤って)推奨される方法を使用することは非常に悪い考えであると収集できました。アニメーションのfillModeおよびremovedOnCompletionプロパティ。この方法は、モデル層とプレゼンテーション層の間に不一致が生じるため、多くの人にとって悪い習慣と見なされています。そのため、これらの層の1つに対する将来のクエリは、ユーザーに表示されているものと一致しない可能性があります。

代わりに、アニメーションを実行するための推奨される方法は、アニメーションをアニメーション化するレイヤーにアニメーションを追加すると同時にモデルレイヤーを更新することです。しかし、これがどのように機能しているかを正確に理解するのに苦労しています。私のアニメーションはシンプルで、次のようになります。

CATransform3D updatedTransform = [self newTransformWithCurrentTransform];
// Location 1
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
transformAnimation.duration = 1;
transformAnimation.fromValue = [NSValue valueWithCATransform3D:self.layerBeingAnimated.transform]; // Does not work without this.
transformAnimation.toValue = [NSValue valueWithCATransform3D:updatedTransform];
// Location 2
[self.layerBeingAnimated addAnimation:transformAnimation forKey:kTransformAnimationKey];
// Location 3

コードを使用してモデルレイヤーを更新しようとした3つの場所を示しました

self.layerBeingAnimated.transform = updatedTransform;

場所1では、レイヤーはnewTransformに直接ジャンプし、アニメーション化されません。場所2では、レイヤーは現在の変換からnewTransformに希望どおりにアニメーション化します。場所3では、レイヤーはnewTransformに直接ジャンプし、古い変換に戻り、fromValueからnewTransformに正しくアニメーション化してから、newTransformに留まります。

ここでの取引は何ですか?モデルレイヤーを更新する正しい場所はどこですか?また、これら3つの場所でこのような異なる結果が生成されるのはなぜですか?

ありがとう!

46
user3546182

3つの場所のそれぞれで何が起こっているのかを説明し、最後に「結論」を説明するのが最も簡単だと思います。

また、質問で言及している動作を正確に示すイラストをいくつか追加して、これら3つのことを自分で試したことがない人が簡単に理解できるようにします。また、スタンドアロンレイヤーとバッキングレイヤー(ビューにアタッチされているレイヤー)の両方を示すように図を拡張し、1つある場合の違いについて説明します。

場所1

最初の場所では、アニメーションが作成される前にモデル値が更新されます。これが行われると、transformプロパティはupdatedTransformを保持します。これは、fromValueのレイヤーから変換を読み取ると、updatedValueが返されることを意味します。これは、toとfromの値が同じであるため、アニメーションを表示できないことを意味します。

enter image description here

この場所を期待どおりに機能させることができた1つのことは、新しい値を割り当てる前にoldValueを読み取り、それをfromValueとして使用することです。これは期待どおりに見えます。

// Location 1
CATransform3D oldValue = layer.transform; // read the old value first
layer.transform = updatedTransform;       // then update to the new value

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration  = 1.0;
anim.fromValue = [NSValue valueWithCATransform3D:oldValue];
anim.toValue   = [NSValue valueWithCATransform3D:updatedTransform];

場所2

2番目の例では、from値の変換を読み取ったときに値がまだ更新されていないため、fromValueとtoValueは異なります。その後、モデル値が最終値に更新されます。ここでは、スタンドアロンレイヤーとバッキングレイヤーに実際には違いがありますが、それはわかりません。 CALayerのtransformプロパティはアニメーション化可能であり、値が変更されると自動的に「暗黙の」アニメーションを実行します。これは、「変換」キーパスのレイヤーにアニメーションが追加されることを意味します。ただし、アニメーションブロックの外部で変更が発生した場合、ビューはこの動作を無効にするため、暗黙のアニメーションはありません。

暗黙のアニメーションが表示されない理由は、「明示的な」アニメーションが同じキーパスに対して後で追加されるためです。これは、スタンドアロンレイヤーで2つのアニメーションが実行されている場合でも、どちらの場合も明示的なアニメーションのみが表示されることを意味します(詳細は後で説明します)。慎重に感じている場合は、スタンドアロンレイヤーの暗黙的なアクションを無効にすることができます(これについては後で詳しく説明します)。

enter image description here

場所3

これで最後の場所が残ります。この場合、アニメーションは上記と同じように作成され、fromValueとtoValueが異なります。唯一の違いは、明示的なアニメーションを追加する順序と、暗黙的なアニメーションをトリガーするプロパティを変更する順序です。この場合、暗黙的なアニメーションは明示的なアニメーションの後に追加され、両方とも実行されます(!)。両方のアニメーションは実際にはロケーション2で実行されましたが、明示的な(より長い)アニメーションが以前に追加されたため、表示できませんでした。

enter image description here

すべてが非常に速く動いているので、2つのアニメーションが同時に実行されているときに何が起こっているかを説明するために、レイヤー全体の速度を落としました。このようにして、暗黙のアニメーションが終了したときに何が起こるかを確認するのがはるかに簡単になります。正常に動作するバッキングレイヤーと不正に動作するスタンドアロンレイヤーをオーバーレイし、両方を50%透明にしました。破線の輪郭は元のフレームです。

enter image description here

何が起こっているかについての簡単な説明:青いビューは、それに追加された明示的なアニメーションのみを取得します(1秒の持続時間があります)。オレンジ色のレイヤーには、最初に同じ明示的なアニメーションが追加され、次に暗黙的な0.25秒のアニメーションが追加されます。明示的なアニメーションも暗黙的なアニメーションも「相加的」ではありません。つまり、toValueとfromValueはそのまま使用されます。

免責事項:私はAppleで働いておらず、Core Animationのソースコードを見たことがないので、これから言うのは、状況に基づいた推測ですbehave

私の理解では(免責事項を参照)、これはアニメーションを生成するために画面を更新するたびに発生します。現在のタイムスタンプでは、レイヤーはアニメーションが追加された順序で処理され、プレゼンテーション値が更新されます。この場合、明示的なアニメーションは回転変換を設定し、次に暗黙的なアニメーションが来て、明示的な変換を完全にオーバーライドする別の回転変換を設定します。

アニメーションが「加法」になるように構成されている場合、(非常に強力な)上書きする代わりに、プレゼンテーション値に追加します。付加的なアニメーションを使用しても、順序は重要です。非加法アニメーションは後で来て、すべてを上書きする可能性があります。

暗黙のアニメーションは明示的なアニメーションよりも短いため、アニメーション全体の最初の部分では、値は厳密に暗黙のアニメーション(最後に追加された)から取得されていることがわかります。暗黙的なアニメーションが終了すると、残っているアニメーションは、暗黙的なアニメーションの下で実行されている明示的なアニメーションだけです。したがって、暗黙的なアニメーションが終了すると、明示的なアニメーションはすでに0.25秒進行しており、オレンジ色のレイヤーが最初に戻るのではなく、青いビューと同じ値に戻ることがわかります。

どこでshould値を更新しますか?

この時点で、問題は、2つのアニメーションが追加されないようにするにはどうすればよいでしょうか。また、値をどこで更新する必要があるのでしょうか。値が更新される場所は、2つのアニメーションの存在を妨げるものではありません(ただし、最終結果の外観に影響を与える可能性があります)。

2つのアクションがスタンドアロンレイヤーに追加されないようにするために、すべての「アクション」(アニメーションのより一般的な用語)を一時的に無効にします。

[CATransaction begin];
[CATransaction setDisableActions:YES]; // actions are disabled for now
layer.transform = updatedTransform;
[CATransaction commit];                // until here

これを行うと、1つのアニメーションのみがレイヤーに追加されるため、ロケーション2または3のいずれかが機能します。それは単に好みの問題です。 oldValueを読み取る場合は、場所1を使用することもできます(アクションが無効になっている場合)。

バッキングレイヤーをアニメーション化する場合は、アクションを無効にする必要はありません(ビューが無効にします)が、無効にすることも問題ありません。


この時点で、アニメーションを構成する他の方法、追加のアニメーションとは何か、この場合にtoValueとfromValueの両方を指定する必要がある理由について説明し続けることができます。しかし、私はあなたが尋ねた質問に答えたと思います、そしてこの答えはすでに少し長い面にあります。

74