我有一个UIView
子类,在CAShapeLayer
上使用CALayer
掩码。面具使用不同的形状,在剩下的角落有三个圆角和一个切出的矩形。
当我使用标准动画块调整UIView
的大小时,UIView
本身及其CALayer
调整大小就好了。然而,面具会立即应用,这会导致一些绘图问题。
我尝试使用CABasicAnimation
设置动画调整大小,但是没有任何运气可以让动画调整大小。
我可以以某种方式在面具上实现动画调整大小效果吗?我是否需要摆脱面具,或者我是否需要更改当前绘制蒙版的方式(使用- (void)drawInContext:(CGContextRef)ctx
)。
干杯, 亚历
答案 0 :(得分:41)
我找到了解决这个问题的方法。其他答案部分正确并且有帮助。
以下几点对于理解解决方案非常重要:
为了解决这个问题,我们将不得不自己使用Core Animation,并且不能依赖UIView动画块来为我们工作。
只需使用与CATransaction
相同的持续时间创建[UIView animateWithDuration:...]
即可。这将创建一个单独的动画,但如果你的持续时间和缓动功能相同,它应该与动画块中的其他动画完全一致。
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];
答案 1 :(得分:10)
我使用CAShapeLayer
通过将UIView
设置为该形状图层来屏蔽self.layer.mask
。
每当视图大小发生变化时,要为蒙版设置动画,我会覆盖-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
中的掩码路径。
答案 2 :(得分:5)
CALayer的mask属性不具有动画效果,这解释了你在这方面的运气不足。
面具的绘图是否取决于面具的边框? (你能提供一些代码吗?)掩码是否设置了needsDisplayOnBoundsChange属性?
干杯, 科林
答案 3 :(得分:5)
为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
答案 4 :(得分:2)
mask参数不会设置动画,但您可以为设置为蒙版的图层设置动画...
如果为CAShapeLayer的Path属性设置动画,则应该为蒙版设置动画。我可以验证这是否适用于我自己的项目。不确定使用非矢量蒙版。你试过动画掩码的内容属性吗?
谢谢, 乔恩
答案 5 :(得分:0)
我找不到任何编程解决方案,所以我只是绘制一个具有正确形状和alpha值的png图像并使用它。这样我就不需要使用面具......
答案 6 :(得分:0)
可以为蒙版更改设置动画。
我更喜欢使用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];
}
答案 7 :(得分:0)
基于Kekoa's solution的快速3 + 答案:
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()