12

ボタンを押したままにするとブロックが下に動き、離すと元の位置に戻るアニメーションを実現しようとしていますが、アニメーションブロックの現在の位置を取得できません。これが私のコードです:

-(IBAction)moveDown:(id)sender{

   CGRect position = [[container.layer presentationLayer] frame];

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)];

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 310)];

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    moveAnim.path = movePath.CGPath;
    moveAnim.removedOnCompletion = NO;
    moveAnim.fillMode = kCAFillModeForwards;


    CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil];
    animGroup.duration = 2.0;
    animGroup.removedOnCompletion = NO;
    animGroup.fillMode = kCAFillModeForwards;

    [container.layer addAnimation:animGroup forKey:nil];
}
-(IBAction)moveUp:(id)sender{
     CGRect position = [[container.layer presentationLayer] frame];

    UIBezierPath *movePath = [UIBezierPath bezierPath];

    [movePath moveToPoint:CGPointMake(container.frame.origin.x, position.y)];

    [movePath addLineToPoint:CGPointMake(container.frame.origin.x, 115)];

    CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    moveAnim.path = movePath.CGPath;
    moveAnim.removedOnCompletion = NO;
    moveAnim.fillMode = kCAFillModeForwards;

    CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim, nil];
    animGroup.duration = 2.0;
    animGroup.removedOnCompletion = NO;
    animGroup.fillMode = kCAFillModeForwards;

    [container.layer addAnimation:animGroup forKey:nil];

}

しかし、ライン

   CGRect position = [[container.layer presentationLayer] frame];

現在の位置ではなく、目的の位置のみを返しています。基本的に、ボタンを離すとアニメーション化されるコンテナの現在の位置を取得する必要があるため、次のアニメーションを実行できます。私が今持っているものは機能しません。

4

1 に答える 1

46

私はあなたのコードを十分に分析していないので、[[container.layer presentationLayer] frame]期待どおりの結果が得られない理由を 100% 確信できません。しかし、いくつかの問題があります。

明らかな問題の 1 つは、moveDown:が宣言されていないことmovePathです。IfmovePathがインスタンス変数の場合、 が呼び出されるたびにそれをクリアするか、新しいインスタンスを作成することmoveDown:をお勧めしますが、そうしているとは思いません。

あまり目立たない問題は、( と の使用から判断するremovedOnCompletionfillMode、 を使用しているにもかかわらずpresentationLayer) Core Animation がどのように機能するかを明らかに理解していないことです。これは意外とよくあることなので、間違っていたらご容赦ください。とにかく、読み進めてください。Core Animation の仕組みと問題の解決方法を説明します。

Core Animation では、通常作業するレイヤー オブジェクトはモデル レイヤーです。レイヤーにアニメーションをアタッチすると、コア アニメーションはプレゼンテーション レイヤーと呼ばれるモデル レイヤーのコピーを作成し、アニメーションは時間の経過とともにプレゼンテーション レイヤーのプロパティを変更します。アニメーションがモデル レイヤーのプロパティを変更することはありません。

アニメーションが終了し、(デフォルトでは) が削除されると、プレゼンテーション レイヤーが破棄され、モデル レイヤーのプロパティの値が再び有効になります。そのため、画面上のレイヤーは、元の位置/色/その他に「スナップバック」したように見えます。

これを修正するためのよくある、しかし間違った方法は、アニメーションの とその を に設定removedOnCompletionするNOことfillModeですkCAFillModeForwards。これを行うと、プレゼンテーション層がぶらぶらするので、画面に「スナップバック」はありません。問題は、プレゼンテーション層がモデル層とは異なる値でぶらぶらしていることです。モデル レイヤー (またはそれを所有するビュー) にアニメーション化されたプロパティの値を要求すると、画面上の値とは異なる値が返されます。また、プロパティを再度アニメーション化しようとすると、アニメーションが間違った場所から開始される可能性があります。

レイヤー プロパティをアニメーション化して「固定」するには、モデル レイヤーのプロパティ値を変更してから、アニメーションを適用する必要があります。そうすれば、アニメーションが削除され、プレゼンテーション レイヤーがなくなると、画面上のレイヤーはまったく同じように見えます。モデル レイヤーには、アニメーションが終了したときのプレゼンテーション レイヤーと同じプロパティ値があるからです。

キーフレームを使用して直線モーションをアニメートする理由、またはアニメーション グループを使用する理由がわかりません。ここではどちらも必要ないようです。そして、あなたの 2 つのメソッドは実質的に同じなので、共通のコードを除外しましょう。

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y {
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:fromValue];
    animation.toValue = [NSValue valueWithCGPoint:toValue];
    animation.duration = 2;
    [layer addAnimation:animation forKey:animation.keyPath];
}

アニメーションをレイヤーに追加するときに、アニメーションにキーを与えていることに注意してください。毎回同じキーを使用するため、前のアニメーションがまだ終了していない場合、新しいアニメーションごとに前のアニメーションが置き換えられます (削除されます)。

もちろん、これで遊んでみるとmoveUp:moveDown:が半分しか終わっていない場合でも、moveUp:アニメーションの持続時間は 2 秒ですが、移動距離は半分しかないため、アニメーションが半分の速度で表示されることがわかります。 . 実際には、移動距離に基づいて期間を計算する必要があります。

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY {
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:fromValue];
    animation.toValue = [NSValue valueWithCGPoint:toValue];
    animation.duration = 2.0 * (toValue.y - fromValue.y) / (y - baseY);
    [layer addAnimation:animation forKey:animation.keyPath];
}

アニメーション グループのキーパス アニメーションであることが本当に必要な場合は、質問によって、それらが必要な理由がわかるはずです。とにかく、それはそれらのものでも動作します:

- (void)animateLayer:(CALayer *)layer toY:(CGFloat)y withBaseY:(CGFloat)baseY {
    CGPoint fromValue = [layer.presentationLayer position];
    CGPoint toValue = CGPointMake(fromValue.x, y);

    layer.position = toValue;

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:fromValue];
    [path addLineToPoint:toValue];

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = path.CGPath;
    animation.duration =  2.0 * (toValue.y - fromValue.y) / (y - baseY);

    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.duration = animation.duration;
    group.animations = @[animation];
    [layer addAnimation:group forKey:animation.keyPath];
}

私のテスト プログラムの完全なコードは、この gist にあります。新しいシングル ビュー アプリケーション プロジェクトを作成ViewController.mし、内容を要点の内容に置き換えるだけです。

于 2012-12-20T04:50:04.337 に答える