动画CALayer的蒙版大小更改

问题描述 投票:27回答:8

我有一个UIView子类,在其CAShapeLayer上使用CALayer面具。面具使用不同的形状,在剩下的角落有三个圆角和一个切出的矩形。

当我使用标准动画块调整我的UIView大小时,UIView本身及其CALayer调整得很好。然而,面具立即应用,这导致一些绘图问题。

我已经尝试使用CABasicAnimation动画制作面具的大小调整,但没有任何运气来调整动画大小。

我可以以某种方式在面具上实现动画调整大小效果吗?我是否需要摆脱面具,或者我必须改变一下我目前绘制面具的方式(使用- (void)drawInContext:(CGContextRef)ctx)。

干杯,亚历克斯

iphone objective-c core-animation calayer mask
8个回答
43
投票

我找到了解决这个问题的方法。其他答案部分正确并且有帮助。

以下几点对于理解解决方案非常重要:

  • mask属性本身不可动画。
  • 由于蒙版是CALayer,因此可以自行设置动画。
  • 帧不可动画,使用边界和位置。这可能不适用于您(如果您没有尝试为框架设置动画),但这对我来说是一个问题。 (见Apple QA 1620
  • 视图图层的蒙版未绑定到UIView,因此它不会接收应用于视图图层的核心动画事务。
  • 我们正在直接修改CALayer,因此我们不能指望UIView会知道我们要做什么,因此UIView动画不会创建核心动画事务以包含对属性的更改。

为了解决这个问题,我们将不得不自己使用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];

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中的遮罩路径。


5
投票

CALayer的mask属性不具有动画效果,这解释了你在这方面缺乏运气。

面具的绘制是否取决于面具的边框/边界? (你能提供一些代码吗?)掩码是否设置了needsDisplayOnBoundsChange属性?

干杯,科林


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

2
投票

mask参数不会设置动画,但您可以为设置为蒙版的图层设置动画...

如果为CAShapeLayer的Path属性设置动画,则应该为蒙版设置动画。我可以验证这是否适用于我自己的项目。不确定使用非矢量蒙版。你试过动画掩码的内容属性吗?

谢谢,乔恩


0
投票

我找不到任何编程解决方案,所以我只是绘制一个具有正确形状和alpha值的png图像并使用它。这样我就不需要使用面具......


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];
}

0
投票

基于Kekoa's solution的Swift 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()
© www.soinside.com 2019 - 2024. All rights reserved.