web-dev-qa-db-ja.com

CABasicAnimationは、アニメーションの完了後に初期値にリセットされます

CALayerを回転させて、アニメーションの完了後に最終位置で停止しようとしています。

ただし、アニメーションが完了すると、初期位置にリセットされます。

(xcodeドキュメントは、アニメーションがプロパティの値を更新しないと明示的に述べています。)

これを達成する方法の提案。

130
Nilesh Ukey

編集:ここに答えがあります。それは私の答えとクリシュナンの組み合わせです。

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

デフォルト値はkCAFillModeRemovedです。 (表示されているリセット動作です。)

260
Nilesh Ukey

RemovedOnCompletionの問題は、UI要素がユーザーの操作を許可しないことです。

テクニックは、アニメーションのFROM値とオブジェクトのTO値を設定することです。アニメーションは、開始前にTO値を自動的に入力し、削除されるとオブジェクトを正しい状態のままにします。

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];
78
Leslie Godwin

次のプロパティを設定します。

animationObject.removedOnCompletion = NO;
16
Krishnan

コードの中に置くだけです

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;
12
msk_sureshkumar

レイヤーに追加するときに、単にCABasicAnimationのキーをpositionに設定できます。これにより、実行ループ内の現在のパスの位置で行われた暗黙のアニメーションがオーバーライドされます。

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

2011 Apple WWDC Video Session 421-Core Animation Essentials(ビデオの中央)

11
yageek

CALayerには、モデルレイヤーとプレゼンテーションレイヤーがあります。アニメーション中、プレゼンテーション層はモデルとは独立して更新されます。アニメーションが完了すると、プレゼンテーションレイヤーがモデルの値で更新されます。アニメーションの終了後、不快なジャンプを避けたい場合は、2つのレイヤーの同期を保つことが重要です。

最終値がわかっている場合は、モデルを直接設定できます。

self.view.layer.opacity = 1;

ただし、終了位置がわからないアニメーション(ユーザーが一時停止してから元に戻すことができるスローフェードなど)がある場合は、プレゼンテーションレイヤーを直接クエリして現在の値を見つけ、モデルを更新できます。

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

プレゼンテーションレイヤーから値を取得することも、スケーリングまたは回転キーパスに特に役立ちます。 (例:transform.scaletransform.rotation

7
Jason Moore

単にfillModeremovedOnCompletionを設定してもうまくいきませんでした。以下のプロパティのallをCABasicAnimationオブジェクトに設定することで問題を解決しました。

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

このコードは、myViewをそのサイズの85%に変換します(3次元は変更されません)。

removedOnCompletionを使用せずに

このテクニックを試すことができます:

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}
5
Ayman Ibrahim

これは動作します:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

コアアニメーションは、アニメーション中に通常のレイヤーの上に「プレゼンテーションレイヤー」を表示します。そのため、アニメーションが終了してプレゼンテーションレイヤーが消えたときに表示されるように、不透明度(または何でも)を設定します。行でこれを行いますbeforeアニメーションを追加して、完了時のちらつきを防ぎます。

遅延が必要な場合は、次を実行します。

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

最後に、「removedOnCompletion = false」を使用すると、レイヤーが最終的に破棄されるまでCAAnimationsがリークします。

4
Chris

@Leslie Godwinの答えは本当に良くありません、「self.view.layer.opacity = 1;」すぐに完了します(約1秒かかります)。疑問がある場合は、alphaAnimation.durationを10.0に修正してください。この行を削除する必要があります。

そのため、fillModeをkCAFillModeForwardsに修正し、removeOnCompletionをNOに修正すると、アニメーションはレイヤーに残ります。アニメーションデリゲートを修正して、次のようなことを試してください。

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

...この行を実行すると、レイヤーはすぐに復元されます。それは避けたいものです。

アニメーションを削除する前に、レイヤープロパティを修正する必要があります。これを試して:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}
4
tontonCD

したがって、私の問題は、パンジェスチャでオブジェクトを回転させようとしていたため、各移動で複数の同一のアニメーションがあったことです。私は両方を持っていたfillMode = kCAFillModeForwardsおよびisRemovedOnCompletion = falseしかし、それは助けにはなりませんでした。私の場合、新しいアニメーションを追加するたびにアニメーションキーが異なることを確認する必要がありました

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = kCAFillModeForwards

head.layer.add(rotate, forKey: "rotate\(angle)")
2
sunshinejr

コアアニメーションは、2つのレイヤー階層 モデルレイヤー および プレゼンテーションレイヤー を維持します。アニメーションの進行中、モデルレイヤーは実際にはそのままで、初期値を保持します。デフォルトでは、アニメーションは完了すると削除されます。次に、プレゼンテーションレイヤーはモデルレイヤーの値にフォールバックします。

removedOnCompletionNOに設定するだけで、アニメーションが削除されず、メモリが浪費されることを意味します。さらに、モデルレイヤーとプレゼンテーションレイヤーは同期しなくなるため、潜在的なバグが発生する可能性があります。

そのため、モデルレイヤーのプロパティを直接最終値に更新する方が適切なソリューションになります。

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

上記のコードの最初の行によって暗黙的なアニメーションが発生している場合は、オフにします:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
2
Lizhen Hu

RemovedOnCompletionフラグがfalseに設定され、fillModeがkCAFillModeForwardsに設定されていることも、私にとっては機能しないようです。

レイヤーに新しいアニメーションを適用すると、アニメーション化オブジェクトが初期状態にリセットされ、その状態からアニメーション化されます。さらに、次のように新しいアニメーションを設定する前に、プレゼンテーションレイヤーのプロパティに従ってモデルレイヤーの目的のプロパティを設定する必要があります。

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];
1

最も簡単な解決策は、暗黙的なアニメーションを使用することです。これにより、すべての問題が処理されます。

self.layer?.backgroundColor = NSColor.red.cgColor;

カスタマイズしたい場合期間、NSAnimationContextを使用できます:

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

注:これはmacOSでのみテストされています。

これを行うときに、最初はアニメーションが表示されませんでした。問題は、view-backedレイヤーのレイヤーがnot暗黙のアニメーションを行うことです。これを解決するには、必ず自分でレイヤーを追加してください(ビューをレイヤーに設定する前に)。

これを行う方法の例は次のとおりです。

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

self.wantsLayerを使用してもテストに違いはありませんでしたが、わからない副作用が発生する可能性があります。

0
paxos

遊び場のサンプルを次に示します。

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. アニメーションモデルを作成する
  2. 開始位置の設定アニメーションの(スキップできます。現在のレイヤーレイアウトによって異なります)
  3. 終了位置を設定アニメーションの
  4. アニメーション期間を設定する
  5. アニメーションを1秒間遅らせる
  6. falseisRemovedOnCompletionに設定しないでください-アニメーションが終了した後、Core Animationを消去してください
  7. ここに、トリックの最初の部分があります-Core Animationに言って、アニメーションを開始位置(ステップ#2で設定)に配置しますアニメーションも開始されました-時間的に後方に拡張します
  8. 準備されたアニメーションオブジェクトをコピーし、レイヤーに追加し、遅延後にアニメーションを開始します(手順5で設定します)
  9. 2番目の部分は、正しい終了位置を設定することですレイヤーの-アニメーションが削除された後、レイヤーが表示されます正しい場所
0
adnako