10

境界内に不透明な境界線がある半透明の円を示す UIView を作成しようとしています。-[UIView animateWithDuration:animations:]ブロック内と、1秒間に数回起動するピンチジェスチャ認識アクションの2つの方法で境界を変更できるようにしたいと考えています。SOの他の場所の回答に基づいて3つのアプローチを試しましたが、適切なものはありません。

  1. ビューのレイヤーの角の半径を に設定すると、layoutSubviews移動はスムーズになりますが、アニメーション中はビューが円形のままではありません。cornerRadius はアニメーション化できないようです。

  2. 円を描画するdrawRect:と一貫して円形のビューが得られますが、円が大きくなりすぎると、デバイスが円を再描画するのに時間がかかりすぎるため、ピンチ ジェスチャでのサイズ変更が不安定になります。

  3. CAShapeLayer を追加し、そのパス プロパティを に設定しますlayoutSublayersOfLayer。パスは暗黙的にアニメーション化できないため、UIView アニメーション内ではアニメーション化されません。

一貫して円形でスムーズにサイズ変更可能なビューを作成する方法はありますか? ハードウェア アクセラレーションを利用するために使用できる他のタイプのレイヤーはありますか?

アップデート

-[UIView animateWithDuration:animations:]ブロック内の境界を変更したいと言ったときに、私が何を意味するかを拡張するようにコメント者から求められました。私のコードには、サークル ビューを含むビューがあります。circle ビュー (cornerRadius を使用するバージョン)-[setBounds:]は、角の半径を設定するためにオーバーライドします。

-(void)setBounds:(CGRect)bounds
{
    self.layer.cornerRadius = fminf(bounds.size.width, bounds.size.height) / 2.0;
    [super setBounds:bounds];
}

円ビューの境界は次のように設定され-[layoutSubviews]ます。

-(void)layoutSubviews
{
    // some other layout is performed and circleRadius and circleCenter are
    // calculated based on the properties and current size of the view.

    self.circleView.bounds = CGRectMake(0, 0, circleRadius*2, circleRadius*2);
    self.circleView.center = circleCenter;
}

次のように、アニメーションでビューのサイズが変更されることがあります。

[UIView animateWithDuration:0.33 animations:^(void) {
    myView.frame = CGRectMake(x, y, w, h);
    [myView setNeedsLayout];
    [myView layoutIfNeeded];
}];

しかし、これらのアニメーション中に、コーナー半径を持つレイヤーを使用して円ビューを描画すると、おかしな形になります。アニメーションの長さを layoutSubviews に渡すことができないため、適切なアニメーションを 内に追加する必要があります-[setBounds]

4

4 に答える 4

17

「View Programming Guide for iOS」のアニメーションに関するセクションにあるように

UIKit と Core Animation の両方がアニメーションのサポートを提供しますが、各テクノロジによって提供されるサポートのレベルは異なります。UIKit では、UIView オブジェクトを使用してアニメーションを実行します。

古いものを使用してアニメーション化できるプロパティの完全なリスト

[UIView beginAnimations:context:];
[UIView setAnimationDuration:];
// Change properties here...
[UIView commitAnimations];

または新しい

[UIView animateWithDuration:animations:];

(使用している)は次のとおりです。

  • フレーム
  • 境界
  • 中心
  • 変換 (CATransform3D ではなく、CGAffineTransform)
  • アルファ
  • 背景色
  • contentStretch

人々を混乱させるのは、UIView アニメーション ブロック内のレイヤーで同じプロパティ、つまりフレーム、境界、位置、不透明度、背景色をアニメーション化できることです。

同じセクションは次のように続けています。

より高度なアニメーション、または UIView クラスでサポートされていないアニメーションを実行したい場所では、Core Animation とビューの下位レイヤーを使用してアニメーションを作成できます。ビュー オブジェクトとレイヤー オブジェクトは複雑にリンクされているため、ビューのレイヤーに対する変更はビュー自体に影響します。

数行下に、Core Animation のアニメート可能なプロパティのリストが表示されています。

  • レイヤーの境界線 (レイヤーの角が丸くなっているかどうかを含む)

あなたが望んでいる効果を達成するための少なくとも2つの良いオプションがあります:

  • コーナー半径のアニメーション
  • CAShapeLayer を使用してパスをアニメーション化する

どちらの場合も、Core Animation でアニメーションを行う必要があります。複数のアニメーションを 1 つとして実行する必要がある場合は、CAAnimationGroup を作成し、それにアニメーションの配列を追加できます。


アップデート:

他のアニメーションと「同時に」レイヤーでコーナー半径アニメーションを実行することにより、できるだけ少ないコード変更で問題を修正します。同じグループにないアニメーションが完全に同時に終了するとは限らないため、同じ時間に引用符を付けます。実行している他のアニメーションによっては、基本的なアニメーションとアニメーション グループのみを使用する方がよい場合があります。同じビュー アニメーション ブロック内の多くの異なるビューに変更を適用する場合は、CATransactions を調べることができます。

以下のコードは、説明したようにフレームとコーナーの半径をアニメーション化します。

UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
[[circle layer] setCornerRadius:50];
[[circle layer] setBorderColor:[[UIColor orangeColor] CGColor]];
[[circle layer] setBorderWidth:2.0];
[[circle layer] setBackgroundColor:[[[UIColor orangeColor] colorWithAlphaComponent:0.5] CGColor]];
[[self view] addSubview:circle];

CGFloat animationDuration = 4.0; // Your duration
CGFloat animationDelay = 3.0; // Your delay (if any)

CABasicAnimation *cornerRadiusAnimation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
[cornerRadiusAnimation setFromValue:[NSNumber numberWithFloat:50.0]]; // The current value
[cornerRadiusAnimation setToValue:[NSNumber numberWithFloat:10.0]]; // The new value
[cornerRadiusAnimation setDuration:animationDuration];
[cornerRadiusAnimation setBeginTime:CACurrentMediaTime() + animationDelay];

// If your UIView animation uses a timing funcition then your basic animation needs the same one
[cornerRadiusAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

// This will keep make the animation look as the "from" and "to" values before and after the animation
[cornerRadiusAnimation setFillMode:kCAFillModeBoth];
[[circle layer] addAnimation:cornerRadiusAnimation forKey:@"keepAsCircle"];
[[circle layer] setCornerRadius:10.0]; // Core Animation doesn't change the real value so we have to.

[UIView animateWithDuration:animationDuration
                      delay:animationDelay
                    options:UIViewAnimationOptionCurveEaseInOut 
                 animations:^{
                     [[circle layer] setFrame:CGRectMake(50, 50, 20, 20)]; // Arbitrary frame ...
                     // You other UIView animations in here...
                 } completion:^(BOOL finished) {
                     // Maybe you have your completion in here...
                 }]; 
于 2012-05-20T14:02:11.670 に答える
10

Davidに感謝します。これが私が見つけた解決策です。結局、それの鍵であることが判明したのは、ビューのメソッドを使用することでした。これは、レイヤーが返す-[actionForLayer:forKey:]ものではなく、UIView ブロック内で使用されるためです。-[actionForKey]

@implementation SGBRoundView

-(CGFloat)radiusForBounds:(CGRect)bounds
{
    return fminf(bounds.size.width, bounds.size.height) / 2;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.opaque = NO;
        self.layer.backgroundColor = [[UIColor purpleColor] CGColor];
        self.layer.borderColor = [[UIColor greenColor] CGColor];
        self.layer.borderWidth = 3;
        self.layer.cornerRadius = [self radiusForBounds:self.bounds];
    }
    return self;
}

-(void)setBounds:(CGRect)bounds
{
    self.layer.cornerRadius = [self radiusForBounds:bounds];
    [super setBounds:bounds];
}

-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    id<CAAction> action = [super actionForLayer:layer forKey:event];

    if ([event isEqualToString:@"cornerRadius"])
    {
        CABasicAnimation *boundsAction = (CABasicAnimation *)[self actionForLayer:layer forKey:@"bounds"];
            if ([boundsAction isKindOfClass:[CABasicAnimation class]] && [boundsAction.fromValue isKindOfClass:[NSValue class]])
        {            
            CABasicAnimation *cornerRadiusAction = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
            cornerRadiusAction.delegate = boundsAction.delegate;
            cornerRadiusAction.duration = boundsAction.duration;
            cornerRadiusAction.fillMode = boundsAction.fillMode;
            cornerRadiusAction.timingFunction = boundsAction.timingFunction;

            CGRect fromBounds = [(NSValue *)boundsAction.fromValue CGRectValue];
            CGFloat fromRadius = [self radiusForBounds:fromBounds];
            cornerRadiusAction.fromValue = [NSNumber numberWithFloat:fromRadius];

            return cornerRadiusAction;
        }
    }

    return action;
}

@end

ビューが境界に対して提供するアクションを使用することで、適切な期間、塗りつぶしモード、およびタイミング関数を取得でき、最も重要なことにデリゲートを行うことができました。それがなければ、UIView アニメーションの完了ブロックは実行されませんでした。

半径のアニメーションは、ほぼすべての状況で境界のアニメーションに従います。解決しようとしているエッジ ケースがいくつかありますが、基本的にはそこにあります。ピンチ ジェスチャがまだときどきぎくしゃくしていることにも言及する価値があります。

于 2012-06-05T17:33:17.590 に答える
0

CAShapeLayer のパス プロパティは、暗黙的にアニメーション化できませんが、アニメーション化できます。円のパスのサイズを変更する CABasicAnimation を作成するのは非常に簡単です。パスに同じ数の制御点があることを確認してください (たとえば、完全な円弧の半径を変更します)。制御点の数を変更すると、事態は非常に奇妙になります。ドキュメントによると、「結果は未定義です」。

于 2012-05-19T22:16:00.187 に答える