绘图与动画之使用自定义属性与图像掩膜实现灯泡开关动画

时间:2021-11-23 23:14:40

基础知识

本文主要用到的技术主要有自定义属性动画与图像操作。

自定义属性动画

常见的CABasicAnimation和CAKeyframeAnimation只能对CAlayer的默认属性进行操作,而对于自有属性无法实现的动画效果我们可以通过自定义属性动画来实现,自定义属性动画的关键在于动画内容关于该属性的函数。过程如下:

  1. 顾名思义,首先我们要根据动画效果添加相应的自定义属性,即动画的路径是关于此属性的函数。同时,属性的实现需要指定为@dynamic类型。
  2. 覆写 + (BOOL)needsDisplayForKey:(NSString *)key 方法,用来指定更改自定义属性时是否更新视图。
  3. 覆写-(id)actionForKey:(NSString *)event方法,用来返回属性动画,这里的动画只用来改变属性值。
  4. 覆写-(void)drawInContext:(CGContextRef)ctx 方法,根据属性值绘制图像,这样在上一步的动画生成不同属性值时,视图也相应的做出改变,从而实现视图的动画效果。

图像操作

图像操作主要包括图像的创建与绘制、图像掩膜处理以及图像混合模式应用。

  1. 图像的创建与绘制
    CGImageCreate 一个灵活创建图像的方法,但是需要指定所有的位图信息。
    CGBitmapContextCreateImage 根据一个位图上下文创建图像,简单理解就是该上下文截图。
    CGImageCreateWithImageInRect 在大图中截取小图。
    CGContextDrawImage 在图形上下文的指定范围中绘制图像
    以及其它方法,在这里就不再一一介绍。

  2. 图像掩膜处理
    图像的掩膜处理主要有图像掩膜、颜色掩膜以及图像掩膜裁切三种。
    图像掩膜是指将包含透明度信息的图像当做掩膜,而底图像素的最终alpha值=1-掩膜对应像素alpha值;
    CGImageMaskCreate 创建图像掩膜
    CGImageCreateWithMask 将图像掩膜应用到图像并创建新图像
    颜色掩膜需要指定在当前色域(color space)下每一个颜色组成的最小值和最大值,如果图像中像素点的颜色值落在色域中,则该点将不显示。
    以下为RGB色域下颜色掩膜的应用。


    const CGFloat myMaskingColors[6] = {124, 255, 68, 222, 0, 165};
    CGImageRef myColorMaskedImage = CGImageCreateWithMaskingColors (image,
    myMaskingColors);

    CGContextClipToMask 将图像按照掩膜范围进行裁切

  3. 图像混合模式应用
    主要用来将图像与背景混合,根据不同需要可以指定不同的混合模式。
    CGContextSetBlendMode 设置混合模式
    CGContextDrawImage 绘制图像


引言

项目来自苹果官方指南,本文在原来代码的基础上做了一些改动。
效果如下:

绘图与动画之使用自定义属性与图像掩膜实现灯泡开关动画

思路与算法

可以看出,动画的主体由三个灯泡组成,而灯泡开关的动画由灯泡内颜色变化来实现,当灯泡颜色为设定颜色时灯泡为打开,当灯泡颜色为透明时灯泡关闭。
实现这种效果需要做两件事,一个是灯泡轮廓的裁切,另一个是灯泡内颜色的变化。我们拿灯泡来看一下:
绘图与动画之使用自定义属性与图像掩膜实现灯泡开关动画
灯泡的周围为白色填充,灯泡*则是透明的。
于是,灯泡轮廓的裁切我们可以通过颜色(白色)掩膜来实现;而灯泡内颜色可以通过图像直接覆盖背景得到,而灯泡内颜色的变化也就是背景颜色的变化,灯泡关闭时的透明色我们可以通过填充白色再做颜色掩膜来实现,那么,最后的动画也就成了白色到指定颜色之间的动画。
color=(red/255.0,green/255.0,blue/255.0)
whilte=(255/255.0,255/255.0,255/255.0)
我们可以定义一个亮度属性brightness,并定义以下函数:

red’=red+a*brightness+b; //红、绿、蓝相同

当brightness=0时,颜色为white,即red’=255;当brightness=255时,颜色为color,即red’=red;
由此可以解出:
a=red/255-1;
b=255-red;

于是red’=255+(red/255-1)*brightness;

代码实现

首先,需要定义一个灯泡图层,增加亮度和颜色属性;

@interface BulbLayer : CALayer 

@property CGFloat brightness;
@property (nonatomic) CGFloat red, green, blue;
-(void)setColor:(UIColor*)color;

@end

实现灯泡开关动画:

@implementation BulbLayer

@dynamic brightness;

-(void)setColor:(UIColor *)color{
CGColorRef cgColor = color.CGColor;
const CGFloat* colors = CGColorGetComponents( cgColor );
self.red = colors[0] * 255.0;
self.green = colors[1] * 255.0;
self.blue = colors[2] * 255.0;
}

+(BOOL)needsDisplayForKey:(NSString*)key {
if( [key isEqualToString:@"brightness"] )
return YES;
return [super needsDisplayForKey:key];
}

-(id<CAAction>)actionForKey:(NSString *)event {
if( [event isEqualToString:@"brightness"] ) {
CABasicAnimation *theAnimation = [CABasicAnimation
animationWithKeyPath:event];
theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
return theAnimation;
}
return [super actionForKey:event];
}

-(void)drawInContext:(CGContextRef)ctx{
CGFloat brightness = self.brightness;

CGFloat curRed = 255 + (self.red/255-1)*brightness;
CGFloat curGreen = 255 + (self.green/255-1)*brightness;
CGFloat curBlue = 255 + (self.blue/255-1)*brightness;

UIImage *_image = [UIImage imageNamed:@"bulb.png"];
UIGraphicsBeginImageContextWithOptions( _image.size, YES, 1.0f );
CGContextRef currentContext=UIGraphicsGetCurrentContext();
CGRect imageRect=CGContextGetClipBoundingBox( currentContext );

CGContextSetRGBFillColor(currentContext, curRed/255.0, curGreen/255.0, curBlue/255.0, 1);
CGContextFillRect(currentContext, imageRect);//绘制背景
CGContextDrawImage( currentContext, imageRect, _image.CGImage );//用灯泡图像覆盖背景
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


const CGFloat maskingColors[6] = { 248.0, 255.0, 248.0, 255.0, 248.0, 255.0 };
CGRect contextRect = CGContextGetClipBoundingBox( ctx );
CGImageRef finalImage= CGImageCreateWithMaskingColors(image.CGImage, maskingColors);//颜色掩膜
CGContextDrawImage( ctx, contextRect, finalImage );
}

调用动画则需要在view中,在触摸时改变灯泡的亮度值。

//替换默认CALayer为BulbLayer
+(Class)layerClass {
return [BulbLayer class];
}

//设置灯泡颜色
-(void)setColor:(UIColor*)color {
[((BulbLayer*)self.layer) setColor:color];
[self setOn:true];
}

//切换开关
-(void)setOn:(BOOL)on{
if( on ) {
if( self.on ) return;
_on = on;
} else {
if( !self.on ) return;
_on = on;
}
((BulbLayer*)self.layer).brightness = ((int)on) * 255;

}

//触摸事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self setOn:!self.on];
}