前言
同PS、AE一样,iOS也有自己的渐变图层(CAGradientLayer)和遮罩图层(CALayer.mask)。此处为了实现下图的动态切换效果,综合运用了渐变图层和遮罩图层。
分析
为了实现颜色的填充动画,可以采用渐变图层改变其渐变色位置的方式来实现。
渐变图层(CAGradientLayer)
-
首先,实例化一个
CAGradientLayer
渐变图层CAGradientLayer *colorLayer = [[CAGradientLayer alloc] init];
-
通过设置属性
startPoint
和endPoint
来确定其颜色的渐变方向,这两点所确定的直线方向就是颜色渐变的方向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];
⚠️注:
CAGradientLayer
图层的colors
属性是一个CGColorRef
对象数组,而UIColor
的CGColor
属性返回的正是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;
-
通过
locations
属性设置每个渐变颜色的位置,此处locations
数组中的元素一一对应步骤3中的colors
数组的元素。因为此处我们的动画是从控件70%位置开始,所以预设四个渐变颜色的位置都为0.7。colorLayer.locations = @[@(0.7), @(0.7), @(0.7), @(0.7)];
-
随后设置渐变图层
frame
与按钮控件相同,并将其添加至按钮图层上。colorLayer.frame = CGRectMake(0, 0, self.width, self.height); [self.layer addSublayer:colorLayer];
-
初始化渐变图层后,就开始对渐变颜色进行动画,此处主要是对渐变图层的
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)
-
如果你按照以上步骤一步步做到此处,你会发现颜色渐变动画是可以了,但是动画却覆盖了整个按钮,整个按钮一片红灿灿。为了达到文章开头Gif所示的动态文字效果,这时就需要用到
CALayer.mask
了。通过CALayer
的mask
属性,我们可以很方便的给某个图层添加遮罩。所谓遮罩,我们可以简单的这样理解:如果图层B是图层A的遮罩图层,那么只要位于图层B透明部分下方的图层A不可见,位于图层B不透明部分下方的图层A可以见。
此处我们从设计姐姐那里要来了一张带有
Fou you
字样的图片,图片除了Fou you
文字外其他地方为透明。 -
实例化一个
CALayer
,用作遮罩图层CALayer *maskLayer = [[CALayer alloc] init];
-
设置
maskLayer
的contents
为预先准备好的图片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;
-
-
最后将
maskLayer
图层设置为上文中colorLayer
图层的遮罩图层。colorLayer.mask = maskLayer;
⚠️此处需要注意,是设置
colorLayer
图层的遮罩,而非设置按钮本身图层的遮罩。因为我们需要的是渐变的颜色部分可见 -
至此,点击
For you
按钮,从第二个字母o
处开始往两边填充红色的动画就完成了。而从两侧逐渐收回红色的动效,只需要对上述颜色渐变进行反向动画即可,此处不再额外讲述。
⚠️可能会遇到的问题
-
在做渐变颜色的反响回收动画时,你可能会遇到在动画结束后,按钮图标字母
o
处依旧留有一丝红色的问题这是因为渐变色红色设置的
location
为0.7,动画结束后,红色并没有消去,而是停留在了动画结束的位置。此时你只需要在动画代理回调方法中,将渐变图层的colors
置为nil
即可。colorLayer.colors = nil;
-
如有其他问题,欢迎留言讨论!
收尾
本文比较浅显的讲述了如何结合CAGradientLayer
和CALayer.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;
}
}
}
}
}