iOS开发|渐变与遮罩图层初探

CAGradientLayer, Mask

Posted by Jack on December 1, 2014

前言

同PS、AE一样,iOS也有自己的渐变图层(CAGradientLayer)和遮罩图层(CALayer.mask)。此处为了实现下图的动态切换效果,综合运用了渐变图层和遮罩图层。

Foryou效果

分析

为了实现颜色的填充动画,可以采用渐变图层改变其渐变色位置的方式来实现。

渐变图层(CAGradientLayer)
  1. 首先,实例化一个CAGradientLayer渐变图层

    CAGradientLayer *colorLayer = [[CAGradientLayer alloc] init];
    
  2. 通过设置属性startPointendPoint来确定其颜色的渐变方向,这两点所确定的直线方向就是颜色渐变的方向

    colorLayer.startPoint = CGPointMake(0.0, 0.4);
    colorLayer.endPoint = CGPointMake(1.0, 0.6);
    
  3. 然后是比较重要的一步,设置渐变颜色,因为动画效果是从中间往两边填充红色,所以此处设置了四个颜色值,边缘两部为透明色,中间两部为红色,透明色+红色两两配对方便后续进行动画设置。

    colorLayer.colors = @[(__bridge id)[UIColor clearColor].CGColor, (__bridge id)[UIColor colorWithHex:0xF24557].CGColor,(__bridge id)[UIColor colorWithHex:0xF24557].CGColor, (__bridge id)[UIColor clearColor].CGColor];
    

    ⚠️注:CAGradientLayer图层的colors属性是一个CGColorRef对象数组,而UIColorCGColor属性返回的正是CGColorRef对象,所以只需将CF对象通过__bridge桥接转化为OC对象进行数组赋值即可。

    @interface CAGradientLayer : CALayer

    /* The array of CGColorRef objects defining the color of each gradient

    • stop. Defaults to nil. Animatable. */

    @property(nullable, copy) NSArray *colors;

    @interface UIColor : NSObject

    @property(nonatomic,readonly) CGColorRef CGColor;

  4. 通过locations属性设置每个渐变颜色的位置,此处locations数组中的元素一一对应步骤3中的colors数组的元素。因为此处我们的动画是从控件70%位置开始,所以预设四个渐变颜色的位置都为0.7。

    colorLayer.locations = @[@(0.7), @(0.7), @(0.7), @(0.7)];
    
  5. 随后设置渐变图层frame与按钮控件相同,并将其添加至按钮图层上。

    colorLayer.frame = CGRectMake(0, 0, self.width, self.height);
    [self.layer addSublayer:colorLayer];
    
  6. 初始化渐变图层后,就开始对渐变颜色进行动画,此处主要是对渐变图层的locations属性进行动画,从而达到红色从中间往两端扩散的效果。

    CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"locations"];
    colorAnimation.fromValue = @[@(0.7), @(0.7), @(0.7), @(0.7)];
    colorAnimation.toValue = @[@(0.0), @(0.0), @(1.0), @(1.0)];
    colorAnimation.duration = 0.3;
    colorAnimation.repeatCount = 1;
    colorAnimation.fillMode = kCAFillModeForwards;
    colorAnimation.removedOnCompletion = NO;
    [colorLayer addAnimation:colorAnimation forKey:@"selectedAnimation"];
    
遮罩图层(CALayer.mask)
  1. 如果你按照以上步骤一步步做到此处,你会发现颜色渐变动画是可以了,但是动画却覆盖了整个按钮,整个按钮一片红灿灿。为了达到文章开头Gif所示的动态文字效果,这时就需要用到CALayer.mask了。通过CALayermask属性,我们可以很方便的给某个图层添加遮罩。

    所谓遮罩,我们可以简单的这样理解:如果图层B是图层A的遮罩图层,那么只要位于图层B透明部分下方的图层A不可见,位于图层B不透明部分下方的图层A可以见。

    此处我们从设计姐姐那里要来了一张带有Fou you字样的图片,图片除了Fou you文字外其他地方为透明。

  2. 实例化一个CALayer,用作遮罩图层

    CALayer *maskLayer = [[CALayer alloc] init];
    
  3. 设置maskLayercontents为预先准备好的图片

    UIImage *maskImage = [UIImage imageNamed:@"For_you"];
    maskLayer.contents = (__bridge id)(maskImage.CGImage);
    

    /* An object providing the contents of the layer, typically a CGImageRef,

    • but may be something else. (For example, NSImage objects are

    • supported on Mac OS X 10.6 and later.) Default value is nil.

    • Animatable. */

    @property(nullable, strong) id contents;

  4. 最后将maskLayer图层设置为上文中colorLayer图层的遮罩图层。

    colorLayer.mask = maskLayer;
    

    ⚠️此处需要注意,是设置colorLayer图层的遮罩,而非设置按钮本身图层的遮罩。因为我们需要的是渐变的颜色部分可见

  5. 至此,点击For you按钮,从第二个字母o处开始往两边填充红色的动画就完成了。而从两侧逐渐收回红色的动效,只需要对上述颜色渐变进行反向动画即可,此处不再额外讲述。

⚠️可能会遇到的问题

  1. 在做渐变颜色的反响回收动画时,你可能会遇到在动画结束后,按钮图标字母o处依旧留有一丝红色的问题

    这是因为渐变色红色设置的location为0.7,动画结束后,红色并没有消去,而是停留在了动画结束的位置。此时你只需要在动画代理回调方法中,将渐变图层的colors置为nil即可。

    colorLayer.colors = nil;
    
  2. 如有其他问题,欢迎留言讨论!

收尾

本文比较浅显的讲述了如何结合CAGradientLayerCALayer.mask来实现颜色填充动画效果,更加深入的运用还有待进一步探索!


附一:渐变色扩散动效源码

// color gradient layer
CAGradientLayer *colorLayer = [[CAGradientLayer alloc] init];
self.colorLayer = colorLayer;
colorLayer.startPoint = CGPointMake(0.0, 0.4);
colorLayer.endPoint = CGPointMake(1.0, 0.6);
colorLayer.colors = @[(__bridge id)[UIColor clearColor].CGColor, (__bridge id)[UIColor colorWithHex:0xF24557].CGColor,(__bridge id)[UIColor colorWithHex:0xF24557].CGColor, (__bridge id)[UIColor clearColor].CGColor];
colorLayer.locations = @[@(0.7), @(0.7), @(0.7), @(0.7)];
colorLayer.frame = CGRectMake(0, 0, self.width, self.height);
[self.layer addSublayer:colorLayer];

// color gradient animation
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"locations"];
colorAnimation.fromValue = @[@(0.7), @(0.7), @(0.7), @(0.7)];
colorAnimation.toValue = @[@(0.0), @(0.0), @(1.0), @(1.0)];
colorAnimation.duration = 0.3;
colorAnimation.repeatCount = 1;
colorAnimation.fillMode = kCAFillModeForwards;
colorAnimation.removedOnCompletion = NO;
[colorLayer addAnimation:colorAnimation forKey:@"selectedAnimation"];
            
// mask layer
CALayer *maskLayer = [[CALayer alloc] init];
maskLayer.backgroundColor = [UIColor clearColor].CGColor;
UIImage *maskImage = [UIImage imageNamed:@"for_you_selected"];
maskLayer.frame = CGRectMake((self.width - maskImage.size.width)/2, (self.height - maskImage.size.height)/2, maskImage.size.width, maskImage.size.height);
maskLayer.contents = (__bridge id)(maskImage.CGImage);
colorLayer.mask = maskLayer;

附二:渐变色回收动效源码

for (CALayer *layer in self.layer.sublayers) {
	if ([layer isKindOfClass:[CAGradientLayer class]]) {
		CAGradientLayer *colorLayer = (CAGradientLayer *)layer;
		CABasicAnimation *colorAnimation = [CABasicAnimation 	animationWithKeyPath:@"locations"];
		colorAnimation.fromValue = @[@(0.0), @(0.0), @(1.0), @(1.0)];
		colorAnimation.toValue = @[@(0.7), @(0.7), @(0.7), @(0.7)];
		colorAnimation.duration = 0.3;
		colorAnimation.repeatCount = 1;
		colorAnimation.delegate = self;
		colorAnimation.fillMode = kCAFillModeForwards;
		colorAnimation.removedOnCompletion = NO;
		[colorLayer addAnimation:colorAnimation forKey:@"unselectedAnimation"];
	}
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    if (self.colorLayer) {
        if ((animation == [self.colorLayer animationForKey:@"unselectedAnimation"]) && flag) {
            for (CALayer *layer in self.layer.sublayers) {
                if ([layer isKindOfClass:[CAGradientLayer class]]) {
                    CAGradientLayer *colorLayer = (CAGradientLayer *)layer;
                    colorLayer.colors = nil;
                }
            }
        }
    }
}