2

の独自のサブクラスであるカスタムボタンがありますUIButtonstartAngleある角度で始まり、ある角度で終わる、円で囲まれた矢印のように見えますendAngle=startAngle+1.5*M_PI。 メソッドstartAngleで使用されるボタンのプロパティです。drawRect:このボタンを押すと、この矢印が中心を中心に 2Pi だけ連続して回転するようにします。なので簡単に使えると思っていたのです[UIView beginAnimations: context:]が、どうやらカスタムプロパティのアニメーションができないので使えないようです。プロパティをアニメーション化するだけなので、CoreAnimation も適していませんCALayer

UIViewでは、iOS でサブクラスのカスタム プロパティのアニメーションを実装する最も簡単な方法は何ですか? それとも、何かを見逃していて、すでに述べた手法で可能ですか?

ありがとうございました。

4

3 に答える 3

2

Jenoxのおかげで、CADisplayLinkを使用してアニメーション コードを更新できました。これは、NSTimer よりも本当に正しい解決策のようです。そこで、CADisplayLink を使用した正しい実装を示します。前のものに非常に近いですが、少し単純です。

プロジェクトに QuartzCore フレームワークを追加します。次に、クラスのヘッダー ファイルに次の行を追加します。

CADisplayLink* timer;
Float32 animationDuration;  // Duration of one animation loop
Float32 initAngle;  // Represents the initial angle 
Float32 angle;  // Angle used for drawing
CFTimeInterval startFrame;  // Timestamp of the animation start

-(void)startAnimation;  // Method to start the animation
-(void)animate;  // Method for updating the property or stopping the animation

実装ファイルで、アニメーションの持続時間とその他の初期値の値を設定します。

initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f;  // Arrow makes full 360° in 1.5 seconds
startFrame=0;  // The animation hasn't been played yet

アニメーションを開始するには、メソッドを呼び出しanimateてアプリケーションのメインの RunLoop に追加するCADisplayLink インスタンスを作成する必要があります。

-(void)startAnimation
{
   timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animate)];
   [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

このタイマーはanimate、アプリケーションの runLoop ごとにメソッドを呼び出します。

それでは、各ループ後にプロパティを更新するためのメソッドの実装について説明します。

-(void)animate
{
   if(startFrame==0) {
      startFrame=timer.timestamp;  // Setting timestamp of start of animation to current moment
      return;  // Exiting till the next run loop
   } 
   CFTimeInterval elapsedTime = timer.timestamp-startFrame;  // Time that has elapsed from start of animation
   Float32 timeProgress = elapsedTime/animationDuration;  // Determine the fraction of full animation which should be shown
   Float32 animProgress = timingFunction(timeProgress); // The current progress of animation
   angle=initAngle+animProgress*2.f*M_PI;  // Setting angle to new value with added rotation corresponding to current animation progress
   if (timeProgress>=1.f) 
   {  // Stopping animation
      angle=initAngle; // Setting angle to initial value to exclude rounding effects
      [timer invalidate];  // Stopping the timer
      startFrame=0;  // Resetting time of start of animation
   }
   [self setNeedsDisplay];  // Redrawing with updated angle value
}

そのため、NSTimer の場合とは異なり、角度プロパティを更新してボタンを再描画する時間間隔を計算する必要がなくなりました。アニメーションの開始から経過した時間をカウントし、この進行状況に対応する値にプロパティを設定するだけです。

そして、アニメーションは NSTimer の場合よりも少しスムーズに動作することを認めなければなりません。デフォルトでは、CADisplayLink はanimate実行ループごとにメソッドを呼び出します。フレームレートを計算したら120fpsでした。あまり効率的ではないと思うので、mainRunLoop に追加する前に CADisplayLink の frameInterval プロパティを変更して、フレーム レートをわずか 22 fps に下げました。

timer.frameInterval=3;

これは、animate最初の実行ループでメソッドを呼び出し、次の 3 つのループでは何もせず、4 番目のループで呼び出すということを意味します。そのため、frameInterval は整数のみにすることができます。

于 2012-04-14T19:09:22.963 に答える
1

タイマーの使用を提案してくれたk06aに再度感謝します。私は NSTimer の操作についていくつかの研究を行いましたが、他の人にも役立つと思うので、実装を示したいと思います。

したがって、私の場合、Float32 angle;矢印全体の描画が開始されるメインプロパティであるある角度から始まる曲線矢印を描画する UIButton サブクラスがありました。つまり、角度の値を変更するだけで、矢印全体が回転します。この回転のアニメーションを作成するには、クラスのヘッダー ファイルに次の行を追加します。

NSTimer* timer;
Float32 animationDuration;  // Duration of one animation loop
Float32 animationFrameRate;  // Frames per second
Float32 initAngle;  // Represents the initial angle 
Float32 angle;  // Angle used for drawing
UInt8 nFrames;  // Number of played frames

-(void)startAnimation;  // Method to start the animation
-(void)animate:(NSTimer*) timer;  // Method for drawing one animation step and stopping the animation

実装ファイルで、アニメーションの継続時間とフレーム レートの値、および描画の初期角度を設定します。

initAngle=0.75*M_PI;
angle=initAngle;
animationDuration=1.5f;  // Arrow makes full 360° in 1.5 seconds
animationFrameRate=15.f;  // Frame rate will be 15 frames per second
nFrames=0;  // The animation hasn't been played yet

アニメーションを開始するには、 1/ animate:(NSTimer*) timer15 秒ごとにメソッドを呼び出す NSTimer インスタンスを作成する必要があります。

-(void)startAnimation
{
   timer = [NSTimer scheduledTimerWithTimeInterval:1.f/animationFrameRate target:self selector:@selector(animate:) userInfo:nil repeats:YES];
}

このタイマーはanimate:すぐにメソッドを呼び出し、手動で停止されるまで 1/15 秒ごとに繰り返します。

それでは、1 つのステップをアニメーション化するためのメソッドの実装について説明します。

-(void)animate:(NSTimer *)timer
{
   nFrames++;  // Incrementing number of played frames
   Float32 animProgress = nFrames/(animationDuration*animationFrameRate); // The current progress of animation
   angle=initAngle+animProgress*2.f*M_PI;  // Setting angle to new value with added rotation corresponding to current animation progress
   if (animProgress>=1.f) 
   {  // Stopping animation when progress is >= 1
      angle=initAngle; // Setting angle to initial value to exclude rounding effects
      [timer invalidate];  // Stopping the timer
      nFrames=0;  // Resetting counter of played frames for being able to play animation again
   }
   [self setNeedsDisplay];  // Redrawing with updated angle value
}

最初に言及したいのは、angle==initAngle丸め効果のために比較線だけが機能しなかったということです。完全に回転した後、それらはまったく同じではありません。そのため、それらが十分に近いかどうかを確認し、角度値を初期値に設定して、アニメーション ループを何度も繰り返した後に角度値の小さなドリフトをブロックします。そして、完全に正しくするために、このコードは角度の変換を常に 0 から 2*M_PI の間で次のように管理する必要があります。

angle=normalizedAngle(initAngle+animProgress*2.f*M_PI);

どこ

Float32 normalizedAngle(Float32 angle)
{
   while(angle>2.f*M_PI) angle-=2.f*M_PI;
   while(angle<0.f) angle+=2.f*M_PI;
   return angle
}

もう 1 つの重要な点は、残念ながら、この種の手動アニメーションに easeIn、easeOut、またはその他のデフォルトの animationCurves を簡単に適用する方法がわからないことです。存在しないと思います。しかし、もちろん、手でそれを行うことは可能です。そのタイミング関数を表す行は

Float32 animProgress = nFrames/(animationDuration*animationFrameRate);

として扱うことができますFloat32 y = x;。これは、時間の速度と同じである線形動作、一定速度を意味します。y = cos(x)しかし、それをor y = sqrt(x)orのように変更してy = pow(x,3.f)、非線形の動作を与えることができます。x が 0 (アニメーションの開始) から 1 (アニメーションの終了) になることを考慮して、自分で考えることができます。

コードの見栄えを良くするには、独立したタイミング関数を作成することをお勧めします。

Float32 animationCurve(Float32 x)
{
  return sin(x*0.5*M_PI);
}

しかし今では、アニメーションの進行状況と時間の間の依存関係は直線的ではないため、アニメーションを停止する指標として時間を使用する方が安全です。(たとえば、矢印を 1.5 回転させて最初の角度に戻すようにしたい場合があります。つまり、animProgress は 0 から 1.5 になり、timeProgress は 0 から 1 になります。)安全のために、時間の進行とアニメーションの進行を分けています。

Float32 timeProgress = nFrames/(animationDuration*animationFrameRate);
Float32 animProgress = animationCurve(timeProgress);

次に、時間の進行状況を確認して、アニメーションを停止するかどうかを決定します。

if(timeProgress>=1.f)
{
   // Stop the animation
}

ところで、誰かがアニメーションの便利なタイミング関数のリストを含むソースを知っている場合は、それらを共有していただければ幸いです.

ビルトインの MacOS X ユーティリティ Grapher は関数を視覚化するのに大いに役立ちます。これにより、アニメーションの進行が時間の進行にどのように依存するかを確認できます。

それが誰かを助けることを願っています...

于 2012-04-13T16:26:17.123 に答える
0
- (void)onImageAction:(id)sender
{
    UIButton *iconButton = (UIButton *)sender;
    if(isExpand)
{
    isExpand = FALSE;

    // With Concurrent Block Programming:
    [UIView animateWithDuration:0.4 animations:^{
        [iconButton setFrame:[[btnFrameList objectAtIndex:iconButton.tag] CGRectValue]];
    } completion: ^(BOOL finished) {
        [self animationDidStop:@"Expand" finished:YES context:nil];
    }];
}
else
{
    isExpand = TRUE;

    [UIView animateWithDuration:0.4 animations:^{
        [iconButton setFrame:CGRectMake(30,02, 225, 205)];
    }];

    for(UIButton *button in [viewThumb subviews])
    {
        [button setUserInteractionEnabled:FALSE];
        //[button setHidden:TRUE];
    }
    [viewThumb bringSubviewToFront:iconButton];

    [iconButton setUserInteractionEnabled:TRUE];
   // [iconButton setHidden:FALSE];
    }
}
于 2013-06-13T13:18:35.927 に答える