UIView
にCAShapeLayer
マスクを使用するCALayer
サブクラスがあります。マスクは独特の形状を使用しており、3つの丸い角と、残りの角に切り抜かれた長方形があります。
標準のアニメーションブロックを使用してUIView
のサイズを変更すると、UIView
自体とそのCALayer
のサイズが適切に変更されます。ただし、マスクは即座に適用されるため、描画の問題が発生します。
CABasicAnimation
を使用してマスクのサイズ変更をアニメーション化しようとしましたが、サイズ変更をアニメーション化することができませんでした。
どういうわけか、マスクにアニメーションのサイズ変更効果を実現できますか?マスクを取り除く必要がありますか、それとも現在マスクを描画する方法について何かを変更する必要がありますか(- (void)drawInContext:(CGContextRef)ctx
を使用)。
乾杯、アレックス
私はこの問題の解決策を見つけました。他の答えは部分的に正しく、役に立ちます。
ソリューションを理解するには、次の点が重要です。
解決するには、Core Animationを自分で利用する必要があり、UIViewアニメーションブロックに依存して作業を行うことはできません。
[UIView animateWithDuration:...]
で使用しているのと同じ期間でCATransaction
を作成するだけです。これにより別のアニメーションが作成されますが、継続時間とイージング機能が同じである場合は、アニメーションブロック内の他のアニメーションと正確にアニメーション化する必要があります。
NSTimeInterval duration = 0.5;// match this to the value of the UIView animateWithDuration: call
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration];
self.myView.layer.mask.position = CGPointMake(newX, 0);
self.myView.layer.mask.bounds = CGRectMake(0, 0, newWidth, newHeight);
[CATransaction commit];
CAShapeLayer
を使用して、self.layer.mask
をそのシェイプレイヤーに設定することにより、UIView
をマスクします。
ビューのサイズが変更されるたびにマスクをアニメーション化するには、アニメーション中に境界が変更された場合に、-setBounds:
を上書きしてマスクレイヤーパスをアニメーション化します。
これが私がそれを実装した方法です:
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
CAPropertyAnimation *boundsAnimation = (CABasicAnimation *)[self.layer animationForKey:@"bounds"];
// update the mask
self.maskLayer.frame = self.layer.bounds;
// if the bounds change happens within an animation, also animate the mask path
if (!boundsAnimation) {
self.maskLayer.path = [self createMaskPath];
} else {
// copying the original animation allows us to keep all animation settings
CABasicAnimation *animation = [boundsAnimation copy];
animation.keyPath = @"path";
CGPathRef newPath = [self createMaskPath];
animation.fromValue = (id)self.maskLayer.path;
animation.toValue = (__bridge id)newPath;
self.maskLayer.path = newPath;
[self.maskLayer addAnimation:animation forKey:@"path"];
}
}
(例では、self.maskLayer
は `self.layer.maskに設定されています)
私の-createMaskPath
は、ビューをマスクするために使用するCGPathRefを計算します。 -layoutSubviews
のマスクパスも更新します。
UIViewのマスクレイヤーの境界の変更をアニメーション化するには:サブクラスUIView、およびCATransactionを使用してマスクをアニメーション化します-Kekodasの回答に似ていますが、より一般的です:
@implementation UIMaskView
- (void) layoutSubviews {
[super layoutSubviews];
CAAnimation* animation = [self.layer animationForKey:@"bounds"];
if (animation) {
[CATransaction begin];
[CATransaction setAnimationDuration:animation.duration];
}
self.layer.mask.bounds = self.layer.bounds;
if (animation) {
[CATransaction commit];
}
}
@end
CALayerのマスクプロパティはアニメートできません。これは、その方向での運の欠如を説明しています。
マスクの描画は、マスクのフレーム/境界に依存しますか? (コードを提供できますか?)マスクにはneedsDisplayOnBoundsChangeプロパティが設定されていますか?
乾杯、コリン
マスクパラメータはアニメートしませんが、マスクとして設定されているレイヤーをアニメートできます。
CAShapeLayerのPathプロパティをアニメーション化すると、マスクがアニメーション化されます。これが自分のプロジェクトで機能することを確認できます。ただし、非ベクトルマスクの使用についてはよくわかりません。マスクのcontentsプロパティをアニメーション化してみましたか?
ありがとう、ジョン
プログラムによる解決策が見つからなかったので、正しい形状とアルファ値でpng画像を描画し、代わりにそれを使用しました。そうすれば、マスクを使う必要はありません...
Swift 3 +Kekoaのソリューション に基づく回答:
let duration = 0.15 //match this to the value of the UIView.animate(withDuration:) call
CATransaction.begin()
CATransaction.setValue(duration, forKey: kCATransactionAnimationDuration)
myView.layer.mask.position = CGPoint(x: [new X], y: [new Y]) //just an example
CATransaction.commit()
マスクの変更をアニメートすることが可能です。
CAShapeLayerをマスクレイヤーとして使用することを好みます。プロパティパスを使用してマスクの変更をアニメーション化すると非常に便利です。
変更をアニメーション化する前に、ソースのコンテンツをインスタンスCGImageRefにダンプし、アニメーション用の新しいレイヤーを作成します。アニメーション中に元のレイヤーを非表示にし、アニメーションが終了したときに表示します。
以下は、プロパティパスにキーアニメーションを作成するためのサンプルコードです。独自のパスアニメーションを作成する場合は、パスに常に同じ数のポイントがあることを確認してください。
- (CALayer*)_mosaicMergeLayer:(CGRect)bounds content:(CGImageRef)content isUp:(BOOL)isUp {
CALayer* layer = [CALayer layer];
layer.frame = bounds;
layer.backgroundColor = [[UIColor clearColor] CGColor];
layer.contents = (id)content;
CAShapeLayer* maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [[UIColor blackColor] CGColor];
maskLayer.frame = bounds;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.path = ( isUp ? [self _maskArrowUp:-bounds.size.height*2] : [self _maskArrowDown:bounds.size.height*2] );
layer.mask = maskLayer;
CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:@"path"];
ani.removedOnCompletion = YES;
ani.duration = 0.3f;
ani.fillMode = kCAFillModeForwards;
ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
NSArray* values = ( isUp ?
[NSArray arrayWithObjects:
(id)[self _maskArrowUp:0],
(id)[self _maskArrowUp:-ceilf(bounds.size.height*1.2)],
nil]
:
[NSArray arrayWithObjects:
(id)[self _maskArrowDown:0],
(id)[self _maskArrowDown:bounds.size.height],
nil]
);
ani.values = values;
ani.delegate = self;
[maskLayer addAnimation:ani forKey:nil];
return layer;
}
- (void)_startMosaicMergeAni:(BOOL)up {
CALayer* overlayer = self.aniLayer;
CGRect bounds = overlayer.bounds;
self.firstHalfAni = NO;
CALayer* frontLayer = nil;
frontLayer = [self _mosaicMergeLayer:bounds
content:self.toViewSnapshot
isUp:up];
overlayer.contents = (id)self.fromViewSnapshot;
[overlayer addSublayer:frontLayer];
}