求助,使用CGContextDrawImageut占用内存缓慢上升迅速上升问题

&最近工作之余在做一个美图秀秀的仿品 做到滤镜这块的时候& 自己就参考了网上几位博主(名字忘了记,非常抱歉)的博客,但是发现跟着他们的demo做的滤镜处理,都会有很严重的内存泄漏,于是就自己按照大体的思路将代码重新整理了下,并解决了内存泄漏问题。
&大体思路如下:
&根据图片创建一个CoreGraphic的图形上文-&根据图形上下文获取图片每个像素的RGBA的色值数组-&遍历数组,按照颜色矩阵进行像素色值调整-&输出绘制新的图片
&具体流程如下:
首先创建一个RGBA通道位图上下文:注意在以下方法中,不要立刻释放malloc方法生成的bitmapData内存空间指针,(可能有的朋友觉得已经把内存空间地址给了位图上下文就可以立马释放掉了,但是由于位图上下文在后来的图像渲染时,仍然需要这一块内存,因此不能在此处立马释放掉内存,之前拜读的几篇博客索性就不释放内存了,因此会导致内存泄漏,处理一些高清图像时,手机内存会轻易飙升到1G以上,而导致程序挂掉)不然会导致位图上下文的内容数据不能正常存在而导致图片生成失败,在这里需要一个全局内存指针来指向它,并且在合适的时候释放内存,具体看如下代码:
创建RGBA通道位图上下文:该位图上下文主要提供了一个画板,配置了画板的绘图所占用的字节数,设备依赖的RGB通道等信息。该上下文主要用于提供所有渲染图像的像素的RGBA值数组,以便后续对像素值的遍历处理。
#pragma mark----------------------------------------&创建一个使用RGBA通道的位图上下文
static CGContextRef CreateRGBABitmapContex(CGImageRef inImage){
CGContextRef context = NULL;
CGColorSpaceRef colorS
void *bitmapD//内存空间的指针,该内存空间的大小等于图像使用RGB通道所占用的字节数。
long bitmapByteC
long bitmapBytePerR
获取像素的横向和纵向个数
size_t pixelsWith = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);
每一行的像素点占用的字节数,每个像素点的RGBA四个通道各占8bit空间
bitmapBytePerRow = (pixelsWith * 4);
整张图片占用的字节数
bitmapByteCount = (bitmapBytePerRow * pixelsHigh);
创建依赖设备的RGB通道
colorSpace = CGColorSpaceCreateDeviceRGB();
分配足够容纳图片字节数的内存空间
bitmapData = malloc(bitmapByteCount);
引用内存地址 以便在合适的地方释放内存空间
bitmap = bitmapD
创建CoreGraphic的图形上下文 该上下文描述了bitmaData指向的内存空间需要绘制的图像的一些绘制参数
context = CGBitmapContextCreate(bitmapData, pixelsWith, pixelsHigh, 8, bitmapBytePerRow, colorSpace, kCGImageAlphaPremultipliedLast);
Core Foundation中含有Create、Alloc的方法名字创建的指针,需要使用CFRelease()函数释放
CGColorSpaceRelease(colorSpace);
此处必须手动释放内存 不然会有内存暴增的现象 但如果在这里释放 真机运行时情况可能就不太好了 (注意:在模拟器上 在此释放不会有任何问题 模拟器的图形上下文和画板机制与真机不同)
free(bitmapData);
返回目标图像的RBGA像素色值的数组指针:该指针指向一个数组,数组中的每四个元素都是图像上的一个像素点的RGBA的数值(0-255),用无符号的char是因为它正好的取值范围就是0-255
static unsigned char *RequestImagePixelData(UIImage * inImage){
CGImageRef img = [inImage CGImage];
CGSize size = [inImage size];
//使用上面的函数创建上下文
CGContextRef cgctx = CreateRGBABitmapContex(img);
CGRect rect = {{0,0},{size.width,size.height}};
//将目标图像绘制到指定的上下文,实际为上下文内的bitmapData。
CGContextDrawImage(cgctx, rect, img);
unsigned char *data = CGBitmapContextGetData(cgctx);
//释放上面的函数创建的上下文
CGContextRelease(cgctx);
cgctx = NULL;
将一个像素RGBA值数组通过一个颜色矩阵进行转换:颜色矩阵决定了图像的渲染效果,因此不同的滤镜效果可以通过设置不同的颜色矩阵进行转换。如果不懂颜色矩阵,可以参考如下的博客:/yjmyzz/archive//1852878.html,在这里就不过多描述了。
注意:在以下方法中,建议先取值并赋值给变量,因为每个像素点的色值,都要调用这个方法,对于一张稍大的高清图,会遍历非常多的次数,因此,里面的每一步多余的操作,都会引起积累起来的长时间处理,博主当时也踩了这个坑,导致处理一张图片时极度耗时。
static void changeRGB(int *red,int* green,int*blue,int*alpha ,const float *f){ //先取值并赋值给变量
int redV = *
int greenV = *
int blueV = *
int alphaV = *
*red = f[0] * redV + f[1]*greenV + f[2]*blueV + f[3] * alphaV + f[4];
*green = f[5] * redV + f[6]*greenV + f[7]*blueV + f[8] * alphaV+ f[9];
*blue = f[10] * redV + f[11]*greenV + f[12]*blueV + f[11] * alphaV+ f[14];
*alpha = f[15] * redV + f[16]*greenV + f[17]*blueV + f[18] * alphaV+ f[19];
//超出边界值的都默认为边界值
if (*red&0) {
if (*red&255) {
*red = 255;
if (*green&0) {
*green = 0;
if (*green&255) {
*green = 255;
if (*blue&0) {
*blue = 0;
if (*blue&255) {
*blue = 255;
if (*alpha&255) {
*alpha=255;
if (*alpha&0) {
*alpha = 0;
以下方法就是暴露给大家的最终图片处理方法了,通过传入一张图片和一个颜色矩阵f,即可完成一张图片的滤镜渲染,并且,在生成一张图片后,最好是将该图像转换为NSData类型进行存储,然后释放掉之前全局变量内存指针,最后再将NSData数据回传给需要的方法。如果不将生成图像转化为NSData存储,而直接使用生成的UIImage对象,则在释放掉内存指针后,UIImage对象也将不存在,楼主亲测,是个大坑,读者尽量避免此类情况。
- (UIImage *)createImageWithImage:(UIImage *)inImage andColorMatrix:(const float *)f{
图片位图像素值数组
unsigned char *imgPixel = RequestImagePixelData(inImage);
CGImageRef inImageRef = [inImage CGImage];
long w = CGImageGetWidth(inImageRef);
long h = CGImageGetHeight(inImageRef);
int wOff = 0;
int pixOff = 0;
遍历修改位图像素值
for (long y = 0; y&h; y++) {
pixOff = wO
for (long x = 0; x&w; x++) {
int red = (unsigned char)imgPixel[pixOff];
int green = (unsigned char)imgPixel[pixOff+1];
int blue = (unsigned char)imgPixel[pixOff +2];
int alpha = (unsigned char)imgPixel[pixOff +3];
changeRGB(&red, &green, &blue, &alpha,f);
imgPixel[pixOff] =
imgPixel[pixOff + 1] =
imgPixel[pixOff + 2] =
imgPixel[pixOff + 3] =
pixOff += 4;
wOff += w * 4 ;
NSInteger dataLength = w * h * 4;
//创建要输出的图像的相关参数
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, imgPixel, dataLength, NULL);
if (!provider) {
NSLog(@"创建输出图像相关参数失败!");
int bitsPerComponent = 8;
int bitsPerPixel = 32;
ItemCount bytesPerRow = 4 *
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderD
CGColorRenderingIntent rederingIntent = kCGRenderingIntentD
//创建要输出的图像
CGImageRef imageRef = CGImageCreate(w, h,bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider,NULL, NO, rederingIntent);
if (!imageRef) {
NSLog(@"创建输出图像失败");
UIImage *my_image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
NSData *data = UIImageJPEGRepresentation(my_image, 1.0);
在这里就可以释放内存了 并且在此之后my_image由于运行时和图形上下文机制已经没有图像内容了 只能使用刚刚生成的图片二进制数据进行图片回传
free(bitmap);
       //这里的block是demo中需要的 可以不做关注
if (_imageBLOCK) {
_imageBLOCK([UIImage imageWithData:data]);
return [UIImage imageWithData:data];
我的demo的github地址为:/China131/JHFilterDemo.git,效果图如下:
demo的操作很简单,即动态改变颜色矩阵的值,实时生成渲染图片,您可以慢慢调试,如果发现您喜欢的渲染类型,直接点击保存图片,Xcode即可打印一个完整的颜色矩阵,您只需要将颜色矩阵保存,就拥有了独一无二的滤镜哦。
阅读(...) 评论()Pages: 1/2
主题 : 关于CGContextDrawImage绘制图片的问题
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
来源于&&分类
关于CGContextDrawImage绘制图片的问题&&&
直接上图吧!我实在是不知道哪里出错了!报错是:Nov 26 22:32:16 ryouhuazhutekiMacBook-Pro.local CGImage[876] &Error&: CGContextDrawImage: invalid context 0x0麻烦各位大大帮忙看一下为什么会出现这个问题...
图片:524A1DC7-DAF1-41B4-8A61-E8A58F6F1F8E.png
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
//将火星图像分裂成两半&&&&UIImage *image = [UIImage imageNamed:@&picker.png&];&&&&CGSize sz = [image size];&&&&&&&&CGImageRef imageLeft = CGImageCreateWithImageInRect([image CGImage], CGRectMake(0, 0, sz.width/2, sz.height));//获取图片左侧部分&&&&CGImageRef imageRight = CGImageCreateWithImageInRect([image CGImage], CGRectMake(sz.width/2, 0, sz.width/2, sz.height));//获取图片右侧部分&&&&UIGraphicsBeginImageContext(CGSizeMake(sz.width*1.5, sz.height));//指定要绘画图片的大小&&&&CGContextRef con = UIGraphicsGetCurrentContext();&&&&//绘制图片&&con:绘制图片的上下文&&CGRectMake:图片的原点和大小&&imageLeft:当前的CGImage&&&&CGContextDrawImage(con, CGRectMake(0, 0, sz.width/2, sz.height), imageLeft);&&&&CGContextDrawImage(con, CGRectMake(sz.width, 0, sz.width/2, sz.height), imageRight);&&&&UIImage *im = UIGraphicsGetImageFromCurrentImageContext();&&&&&&&&UIGraphicsEndImageContext();&&&&CGImageRelease(imageLeft);&&&&CGImageRelease(imageRight);&&&&&&&&UIImageView *imageView = [[UIImageView alloc]initWithImage:im];&&&&imageView.center = self.view.&&&&[self.view addSubview:imageView];代码是这样的....还有我用的是ios6环境!
级别: 侠客
可可豆: 1104 CB
威望: 1104 点
在线时间: 897(时)
发自: Web Page
那个错误应该是CGContext创建不成功。不成功有可能传进去的size有问题。请你查看一下 UIImage *image = [UIImage imageNamed:@&picker.png&]; 这句代码得到的图片是不是为null。可以在这句下面下断点。如果为null的话,就查看有没有picker.png这张图片。如果有这样图片,就查看这个图片所在工程的文件夹是是黄色还是蓝色。如果是蓝色,会在包上生成一个文件夹,这样路径就会出错。上面是猜测,有误就要求更多信息。
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
回 2楼(complex_ok) 的帖子
先谢谢你的回复先!UIImage *image = [UIImage imageNamed:@&picker.png&];首先这句话应该是没有问题的,因为我确定名字和路径是没有错的...然后我昨晚还试了一下,把这两句注释掉的话,程序运行起来是没有问题的.CGContextDrawImage(con, CGRectMake(0, 0, sz.width/2, sz.height), imageLeft);CGContextDrawImage(con, CGRectMake(sz.width, 0, sz.width/2, sz.height), imageRight);是不是CGContextDrawImage需要初始化一下?但是我昨晚找来找去..CGContextDrawImage的构造函数只有一个种方法..难道是因为我传进去的con有错?( CGContextRef con = UIGraphicsGetCurrentContext()&&亦或者IOS6已经不是这样获取了?但是这个方法并没有说过时了?)唉...真心搞不懂!因为我也是看书的写的,前面一直没有问题...求帮助啊!
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
我获取的是这个对象CGContextRef...难道是应该获取CGContext这个对象去绘制?
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
今天必须要得到答案啊....有没有高手把沙发的代码COPY上去试一试...看看到底是什么原因...
级别: 骑士
可可豆: 1814 CB
威望: 1814 点
在线时间: 325(时)
发自: Web Page
回 5楼(rooney0502) 的帖子
CGContext 必需在drawRect 中才能获取到的
级别: 骑士
可可豆: 1814 CB
威望: 1814 点
在线时间: 325(时)
发自: Web Page
少年,我这边拆分成功了。不知道你怎么想
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
回 7楼(notree) 的帖子
不会吧!那我的代码到底哪里有问题啊?能不能把你的那些发给我看一下啊..我实在是弄不明白为什么啊!
级别: 侠客
可可豆: 394 CB
威望: 394 点
在线时间: 274(时)
发自: Web Page
UIGraphicsBeginImageContext(CGSizeMake(sz.width*1.5, sz.height));//指定要绘画图片的大小CGContextRef con = UIGraphicsGetCurrentContext();//绘制图片&&con:绘制图片的上下文&&CGRectMake:图片的原点和大小&&imageLeft:当前的CGImageCGContextDrawImage(con, CGRectMake(0, 0, sz.width/2, sz.height), imageLeft);CGContextDrawImage(con, CGRectMake(sz.width, 0, sz.width/2, sz.height), imageRight);UIImage *im = UIGraphicsGetImageFromCurrentImageContext();我觉得关键在这一部份..我获取CGContextRef,然后传给CGContextDrawImage让他去绘制我想要的图片但为什么会报错呢?是因为我获取方式有问题还是别的问题呢?求帮助啊!IOS新手伤不起啊
Pages: 1/2
关注本帖(如果有新回复会站内信通知您)
发帖、回帖都会得到可观的积分奖励。
按"Ctrl+Enter"直接提交
关注CocoaChina
关注微信 每日推荐
扫一扫 关注CVP公众号
扫一扫 浏览移动版iOS开发系列--让你的应用“动”起来 - IOS - 伯乐在线
& iOS开发系列–让你的应用“动”起来
在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建基础动画、关键帧动画、动画组、转场动画,如何通过UIView的装饰方法对这些动画操作进行简化等。在今天的文章里您可以看到动画操作在iOS中是如何简单和高效,很多原来想做但是苦于没有思路的动画在iOS中将变得越发简单:
CALayer简介
CALayer常用属性
CALayer绘图
关键帧动画
关键帧动画
CALayer简介
在介绍动画操作之前我们必须先来了解一个动画中常用的对象CALayer。CALayer包含在QuartzCore框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中。在使用Core Animation开发动画的本质就是将CALayer中的内容转化为位图从而供硬件操作,所以要熟练掌握动画操作必须先来熟悉CALayer。
在上一篇文章中使用Quartz 2D绘图时大家其实已经用到了CALayer,当利用drawRect:方法绘图的本质就是绘制到了UIView的layer(属性)中,可是这个过程大家在上一节中根本体会不到。但是在Core Animation中我们操作更多的则不再是UIView而是直接面对CALayer。下图描绘了CALayer和UIView的关系,在UIView中有一个layer属性作为根图层,根图层上可以放其他子图层,在UIView中所有能够看到的内容都包含在layer中:
CALayer常用属性
在iOS中CALayer的设计主要是了为了内容展示和动画操作,CALayer本身并不包含在UIKit中,它不能响应事件。由于CALayer在设计之初就考虑它的动画操作功能,CALayer很多属性在修改时都能形成动画效果,这种属性称为“隐式动画属性”。但是对于UIView的根图层而言属性的修改并不形成动画效果,因为很多情况下根图层更多的充当容器的做用,如果它的属性变动形成动画效果会直接影响子图层。另外,UIView的根图层创建工作完全由iOS负责完成,无法重新创建,但是可以往根图层中添加子图层或移除子图层。
下表列出了CALayer常用的属性:
是否支持隐式动画
anchorPoint
和中心点position重合的一个点,称为“锚点”,锚点的描述是相对于x、y位置比例而言的默认在图像中心点(0.5,0.5)的位置
backgroundColor
图层背景颜色
borderColor
borderWidth
图层显示内容,例如可以将图片作为图层内容显示
contentsRect
图层显示内容的大小和位置
cornerRadius
doubleSided
图层背面是否显示,默认为YES
图层大小和位置,不支持隐式动画,所以CALayer中很少使用frame,通常使用bounds和position代替
maskToBounds
子图层是否剪切图层边界,默认为NO
透明度 ,类似于UIView的alpha
图层中心点位置,类似于UIView的center
shadowColor
shadowOffset
阴影偏移量
shadowOpacity
阴影透明度,注意默认为0,如果设置阴影必须设置此属性
shadowPath
阴影的形状
shadowRadius
阴影模糊半径
sublayerTransform
子图层形变
隐式属性动画的本质是这些属性的变动默认隐含了CABasicAnimation动画实现,详情大家可以参照Xcode帮助文档中“Animatable Properties”一节。
在CALayer中很少使用frame属性,因为frame本身不支持动画效果,通常使用bounds和position代替。
CALayer中透明度使用opacity表示而不是alpha;中心点使用position表示而不是center。
anchorPoint属性是图层的锚点,范围在(0~1,0~1)表示在x、y轴的比例,这个点永远可以同position(中心点)重合,当图层中心点固定后,调整anchorPoint即可达到调整图层显示位置的作用(因为它永远和position重合)
为了进一步说明anchorPoint的作用,假设有一个层大小100*100,现在中心点位置(50,50),由此可以得出frame(0,0,100,100)。上面说过anchorPoint默认为(0.5,0.5),同中心点position重合,此时使用图形描述如图1;当修改anchorPoint为(0,0),此时锚点处于图层左上角,但是中心点poition并不会改变,因此图层会向右下角移动,如图2;然后修改anchorPoint为(1,1,),position还是保持位置不变,锚点处于图层右下角,此时图层如图3。
下面通过一个简单的例子演示一下上面几个属性,程序初始化阶段我们定义一个正方形,但是圆角路径调整为正方形边长的一般,使其看起来是一个圆形,在点击屏幕的时候修改图层的属性形成动画效果(注意在程序中没有直接修改UIView的layer属性,因为根图层无法形成动画效果):
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#define WIDTH 50
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self drawMyLayer];
#pragma mark 绘制图层
-(void)drawMyLayer{
CGSize size=[UIScreen mainScreen].bounds.
//获得根图层
CALayer *layer=[[CALayer alloc]init];
//设置背景颜色,由于QuartzCore是跨平台框架,无法直接使用UIColor
layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGC
//设置中心点
layer.position=CGPointMake(size.width/2, size.height/2);
//设置大小
layer.bounds=CGRectMake(0, 0, WIDTH,WIDTH);
//设置圆角,当圆角半径等于矩形的一半时看起来就是一个圆形
layer.cornerRadius=WIDTH/2;
//设置阴影
layer.shadowColor=[UIColor grayColor].CGC
layer.shadowOffset=CGSizeMake(2, 2);
layer.shadowOpacity=.9;
//设置边框
layer.borderColor=[UIColor whiteColor].CGC
layer.borderWidth=1;
//设置锚点
layer.anchorPoint=CGPointZ
[self.view.layer addSublayer:layer];
#pragma mark 点击放大
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[touches anyObject];
CALayer *layer=self.view.layer.sublayers[0];
CGFloat width=layer.bounds.size.
if (width==WIDTH) {
width=WIDTH*4;
width=WIDTH;
layer.bounds=CGRectMake(0, 0, width, width);
layer.position=[touch locationInView:self.view];
layer.cornerRadius=width/2;
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
////&&KCMainViewController.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#define WIDTH 50&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&// Do any additional setup after loading the view.&&&&[self drawMyLayer];}&#pragma mark 绘制图层-(void)drawMyLayer{&&&&CGSize size=[UIScreen mainScreen].bounds.size;&&&&&//获得根图层&&&&CALayer *layer=[[CALayer alloc]init];&&&&//设置背景颜色,由于QuartzCore是跨平台框架,无法直接使用UIColor&&&&layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;&&&&//设置中心点&&&&layer.position=CGPointMake(size.width/2, size.height/2);&&&&//设置大小&&&&layer.bounds=CGRectMake(0, 0, WIDTH,WIDTH);&&&&//设置圆角,当圆角半径等于矩形的一半时看起来就是一个圆形&&&&layer.cornerRadius=WIDTH/2;&&&&//设置阴影&&&&layer.shadowColor=[UIColor grayColor].CGColor;&&&&layer.shadowOffset=CGSizeMake(2, 2);&&&&layer.shadowOpacity=.9;&&&&//设置边框//&&&&layer.borderColor=[UIColor whiteColor].CGC//&&&&layer.borderWidth=1;&&&&&//设置锚点//&&&&layer.anchorPoint=CGPointZ&&&&&[self.view.layer addSublayer:layer];&}&#pragma mark 点击放大-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{&&&&UITouch *touch=[touches anyObject];&&&&CALayer *layer=self.view.layer.sublayers[0];&&&&CGFloat width=layer.bounds.size.width;&&&&if (width==WIDTH) {&&&&&&&&width=WIDTH*4;&&&&}else{&&&&&&&&width=WIDTH;&&&&}&&&&layer.bounds=CGRectMake(0, 0, width, width);&&&&layer.position=[touch locationInView:self.view];&&&&layer.cornerRadius=width/2;}@end
运行效果:
CALayer绘图
上一篇文章中重点讨论了使用Quartz 2D绘图,当时调用了UIView的drawRect:方法绘制图形、图像,这种方式本质还是在图层中绘制,但是这里会着重介绍一下如何直接在图层中绘图。在图层中绘图的方式跟原来基本没有区别,只是drawRect:方法是由UIKit组件进行调用,因此里面可以使用一些UIKit封装的方法进行绘图,而直接绘制到图层的方法由于并非UIKit直接调用因此只能用原生的Core Graphics方法绘制。
图层绘图有两种方法,不管使用哪种方法绘制完必须调用图层的setNeedDisplay方法(注意是图层的方法,不是UIView的方法,前面我们介绍过UIView也有此方法)
通过图层代理drawLayer: inContext:方法绘制
通过自定义图层drawInContext:方法绘制
使用代理方法绘图
通过代理方法进行图层绘图只要指定图层的代理,然后在代理对象中重写-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法即可。需要注意这个方法虽然是代理方法但是不用手动实现CALayerDelegate,因为CALayer定义中给NSObject做了分类扩展,所有的NSObject都包含这个方法。另外设置完代理后必须要调用图层的setNeedDisplay方法,否则绘制的内容无法显示。
下面的代码演示了在一个自定义图层绘制一张图像并将图像设置成圆形,这种效果在很多应用中很常见,如最新版的手机QQ头像就是这种效果:
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//自定义图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
layer.position=CGPointMake(160, 200);
layer.backgroundColor=[UIColor redColor].CGC
layer.cornerRadius=PHOTO_HEIGHT/2;
//注意仅仅设置圆角,对于图形而言可以正常显示,但是对于图层中绘制的图片无法正确显示
//如果想要正确显示则必须设置masksToBounds=YES,剪切子图层
layer.masksToBounds=YES;
//阴影效果无法和masksToBounds同时使用,因为masksToBounds的目的就是剪切外边框,
//而阴影效果刚好在外边框
layer.shadowColor=[UIColor grayColor].CGC
layer.shadowOffset=CGSizeMake(2, 2);
layer.shadowOpacity=1;
//设置边框
layer.borderColor=[UIColor whiteColor].CGC
layer.borderWidth=2;
//设置图层代理
layer.delegate=
//添加图层到根图层
[self.view.layer addSublayer:layer];
//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
NSLog(@"%@",layer);//这个图层正是上面定义的图层
CGContextSaveGState(ctx);
//图形上下文形变,解决图片倒立的问题
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);
UIImage *image=[UIImage imageNamed:@"photo.png"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextDrawPath(ctx, kCGPathFillStroke);
CGContextRestoreGState(ctx);
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
////&&KCMainViewController.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#define PHOTO_HEIGHT 150&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&//自定义图层&&&&CALayer *layer=[[CALayer alloc]init];&&&&layer.bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);&&&&layer.position=CGPointMake(160, 200);&&&&layer.backgroundColor=[UIColor redColor].CGColor;&&&&layer.cornerRadius=PHOTO_HEIGHT/2;&&&&//注意仅仅设置圆角,对于图形而言可以正常显示,但是对于图层中绘制的图片无法正确显示&&&&//如果想要正确显示则必须设置masksToBounds=YES,剪切子图层&&&&layer.masksToBounds=YES;&&&&//阴影效果无法和masksToBounds同时使用,因为masksToBounds的目的就是剪切外边框,&&&&//而阴影效果刚好在外边框//&&&&layer.shadowColor=[UIColor grayColor].CGC//&&&&layer.shadowOffset=CGSizeMake(2, 2);//&&&&layer.shadowOpacity=1;&&&&//设置边框&&&&layer.borderColor=[UIColor whiteColor].CGColor;&&&&layer.borderWidth=2;&&&&&//设置图层代理&&&&layer.delegate=self;&&&&&//添加图层到根图层&&&&[self.view.layer addSublayer:layer];&&&&&//调用图层setNeedDisplay,否则代理方法不会被调用&&&&[layer setNeedsDisplay];}&#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{//&&&&NSLog(@"%@",layer);//这个图层正是上面定义的图层&&&&CGContextSaveGState(ctx);&&&&&//图形上下文形变,解决图片倒立的问题&&&&CGContextScaleCTM(ctx, 1, -1);&&&&CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);&&&&&UIImage *image=[UIImage imageNamed:@"photo.png"];&&&&//注意这个位置是相对于图层而言的不是屏幕&&&&CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);&//&&&&CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));//&&&&CGContextDrawPath(ctx, kCGPathFillStroke);&&&&&CGContextRestoreGState(ctx);}&@end
运行效果:
使用代理方法绘制图形、图像时在drawLayer:inContext:方法中可以通过事件参数获得绘制的图层和图形上下文。在这个方法中绘图时所有的位置都是相对于图层而言的,图形上下文指的也是当前图层的图形上下文。
需要注意的是上面代码中绘制图片圆形裁切效果时如果不设置masksToBounds是无法显示圆形,但是对于其他图形却没有这个限制。原因就是当绘制一张图片到图层上的时候会重新创建一个图层添加到当前图层,这样一来如果设置了圆角之后虽然底图层有圆角效果,但是子图层还是矩形,只有设置了masksToBounds为YES让子图层按底图层剪切才能显示圆角效果。同样的,有些朋友经常在网上提问说为什么使用UIImageView的layer设置圆角后图片无法显示圆角,只有设置masksToBounds才能出现效果,也是类似的问题。
扩展1–带阴影效果的圆形图片裁切
如果设置了masksToBounds=YES之后确实可以显示图片圆角效果,但遗憾的是设置了这个属性之后就无法设置阴影效果。因为masksToBounds=YES就意味着外边框不能显示,而阴影恰恰作为外边框绘制的,这样两个设置就产生了矛盾。要解决这个问题不妨换个思路:使用两个大小一样的图层,下面的图层负责绘制阴影,上面的图层用来显示图片。
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;
//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=
layerShadow.position=
layerShadow.cornerRadius=cornerR
layerShadow.shadowColor=[UIColor grayColor].CGC
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGC
layerShadow.borderWidth=borderW
[self.view.layer addSublayer:layerShadow];
//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=
layer.position=
layer.backgroundColor=[UIColor redColor].CGC
layer.cornerRadius=cornerR
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGC
layer.borderWidth=borderW
//设置图层代理
layer.delegate=
//添加图层到根图层
[self.view.layer addSublayer:layer];
//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
NSLog(@"%@",layer);//这个图层正是上面定义的图层
CGContextSaveGState(ctx);
//图形上下文形变,解决图片倒立的问题
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);
UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextDrawPath(ctx, kCGPathFillStroke);
CGContextRestoreGState(ctx);
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
////&&KCMainViewController.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#define PHOTO_HEIGHT 150&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&CGPoint position= CGPointMake(160, 200);&&&&CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);&&&&CGFloat cornerRadius=PHOTO_HEIGHT/2;&&&&CGFloat borderWidth=2;&&&&&//阴影图层&&&&CALayer *layerShadow=[[CALayer alloc]init];&&&&layerShadow.bounds=bounds;&&&&layerShadow.position=position;&&&&layerShadow.cornerRadius=cornerRadius;&&&&layerShadow.shadowColor=[UIColor grayColor].CGColor;&&&&layerShadow.shadowOffset=CGSizeMake(2, 1);&&&&layerShadow.shadowOpacity=1;&&&&layerShadow.borderColor=[UIColor whiteColor].CGColor;&&&&layerShadow.borderWidth=borderWidth;&&&&[self.view.layer addSublayer:layerShadow];&&&&&//容器图层&&&&CALayer *layer=[[CALayer alloc]init];&&&&layer.bounds=bounds;&&&&layer.position=position;&&&&layer.backgroundColor=[UIColor redColor].CGColor;&&&&layer.cornerRadius=cornerRadius;&&&&layer.masksToBounds=YES;&&&&layer.borderColor=[UIColor whiteColor].CGColor;&&&&layer.borderWidth=borderWidth;&&&&&//设置图层代理&&&&layer.delegate=self;&&&&&//添加图层到根图层&&&&[self.view.layer addSublayer:layer];&&&&&//调用图层setNeedDisplay,否则代理方法不会被调用&&&&[layer setNeedsDisplay];}&#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{&&&&//&&&&NSLog(@"%@",layer);//这个图层正是上面定义的图层&&&&CGContextSaveGState(ctx);&&&&&//图形上下文形变,解决图片倒立的问题&&&&CGContextScaleCTM(ctx, 1, -1);&&&&CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);&&&&&UIImage *image=[UIImage imageNamed:@"photo.jpg"];&&&&//注意这个位置是相对于图层而言的不是屏幕&&&&CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);&&&&&//&&&&CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));&&&&//&&&&CGContextDrawPath(ctx, kCGPathFillStroke);&&&&&CGContextRestoreGState(ctx);}@end
运行效果:
扩展2–图层的形变
从上面代码中大家不难发现使用Core Graphics绘制图片时会倒立显示,对图层的图形上下文进行了反转。在前一篇文章中也采用了类似的方法去解决这个问题,但是在那篇文章中也提到过如果直接让图像沿着x轴旋转180度同样可以达到正确显示的目的,只是当时的旋转靠图形上下文还无法绕x轴旋转。今天学习了图层之后,其实可以控制图层直接旋转而不用借助于图形上下文的形变操作,而且这么操作起来会更加简单和直观。对于上面的程序,只需要设置图层的transform属性即可。需要注意的是transform是CATransform3D类型,形变可以在三个维度上进行,使用方法和前面介绍的二维形变是类似的,而且都有对应的形变设置方法(如:CATransform3DMakeTranslation()、CATransform3DMakeScale()、CATransform3DMakeRotation())。下面的代码通过CATransform3DMakeRotation()方法在x轴旋转180度解决倒立问题:
Objective-C
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;
//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=
layerShadow.position=
layerShadow.cornerRadius=cornerR
layerShadow.shadowColor=[UIColor grayColor].CGC
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGC
layerShadow.borderWidth=borderW
[self.view.layer addSublayer:layerShadow];
//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=
layer.position=
layer.backgroundColor=[UIColor redColor].CGC
layer.cornerRadius=cornerR
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGC
layer.borderWidth=borderW
//利用图层形变解决图像倒立问题
layer.transform=CATransform3DMakeRotation(M_PI, 1, 0, 0);
//设置图层代理
layer.delegate=
//添加图层到根图层
[self.view.layer addSublayer:layer];
//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
#pragma mark 绘制图形、图像到图层,注意参数中的ctx时图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
NSLog(@"%@",layer);//这个图层正是上面定义的图层
UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
////&&形变演示//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#define PHOTO_HEIGHT 150&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&CGPoint position= CGPointMake(160, 200);&&&&CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);&&&&CGFloat cornerRadius=PHOTO_HEIGHT/2;&&&&CGFloat borderWidth=2;&&&&&//阴影图层&&&&CALayer *layerShadow=[[CALayer alloc]init];&&&&layerShadow.bounds=bounds;&&&&layerShadow.position=position;&&&&layerShadow.cornerRadius=cornerRadius;&&&&layerShadow.shadowColor=[UIColor grayColor].CGColor;&&&&layerShadow.shadowOffset=CGSizeMake(2, 1);&&&&layerShadow.shadowOpacity=1;&&&&layerShadow.borderColor=[UIColor whiteColor].CGColor;&&&&layerShadow.borderWidth=borderWidth;&&&&[self.view.layer addSublayer:layerShadow];&&&&&//容器图层&&&&CALayer *layer=[[CALayer alloc]init];&&&&layer.bounds=bounds;&&&&layer.position=position;&&&&layer.backgroundColor=[UIColor redColor].CGColor;&&&&layer.cornerRadius=cornerRadius;&&&&layer.masksToBounds=YES;&&&&layer.borderColor=[UIColor whiteColor].CGColor;&&&&layer.borderWidth=borderWidth;&&&&&//利用图层形变解决图像倒立问题&&&&layer.transform=CATransform3DMakeRotation(M_PI, 1, 0, 0);&&&&&//设置图层代理&&&&layer.delegate=self;&&&&&//添加图层到根图层&&&&[self.view.layer addSublayer:layer];&&&&&//调用图层setNeedDisplay,否则代理方法不会被调用&&&&[layer setNeedsDisplay];}&#pragma mark 绘制图形、图像到图层,注意参数中的ctx时图层的图形上下文,其中绘图位置也是相对图层而言的-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{&&&&//&&&&NSLog(@"%@",layer);//这个图层正是上面定义的图层&&&&UIImage *image=[UIImage imageNamed:@"photo.jpg"];&&&&//注意这个位置是相对于图层而言的不是屏幕&&&&CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);}&@end
事实上如果仅仅就显示一张图片在图层中当然没有必要那么麻烦,直接设置图层contents就可以了,不牵涉到绘图也就没有倒立的问题了。
Objective-C
图层内容设置
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;
//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=
layerShadow.position=
layerShadow.cornerRadius=cornerR
layerShadow.shadowColor=[UIColor grayColor].CGC
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGC
layerShadow.borderWidth=borderW
[self.view.layer addSublayer:layerShadow];
//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=
layer.position=
layer.backgroundColor=[UIColor redColor].CGC
layer.cornerRadius=cornerR
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGC
layer.borderWidth=borderW
//设置内容(注意这里一定要转换为CGImage)
UIImage *image=[UIImage imageNamed:@"photo.jpg"];
layer.contents=(id)image.CGI
[layer setContents:(id)image.CGImage];
//添加图层到根图层
[self.view.layer addSublayer:layer];
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
////&&图层内容设置//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#define PHOTO_HEIGHT 150&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&CGPoint position= CGPointMake(160, 200);&&&&CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);&&&&CGFloat cornerRadius=PHOTO_HEIGHT/2;&&&&CGFloat borderWidth=2;&&&&&//阴影图层&&&&CALayer *layerShadow=[[CALayer alloc]init];&&&&layerShadow.bounds=bounds;&&&&layerShadow.position=position;&&&&layerShadow.cornerRadius=cornerRadius;&&&&layerShadow.shadowColor=[UIColor grayColor].CGColor;&&&&layerShadow.shadowOffset=CGSizeMake(2, 1);&&&&layerShadow.shadowOpacity=1;&&&&layerShadow.borderColor=[UIColor whiteColor].CGColor;&&&&layerShadow.borderWidth=borderWidth;&&&&[self.view.layer addSublayer:layerShadow];&&&&&//容器图层&&&&CALayer *layer=[[CALayer alloc]init];&&&&layer.bounds=bounds;&&&&layer.position=position;&&&&layer.backgroundColor=[UIColor redColor].CGColor;&&&&layer.cornerRadius=cornerRadius;&&&&layer.masksToBounds=YES;&&&&layer.borderColor=[UIColor whiteColor].CGColor;&&&&layer.borderWidth=borderWidth;&&&&//设置内容(注意这里一定要转换为CGImage)&&&&UIImage *image=[UIImage imageNamed:@"photo.jpg"];//&&&&layer.contents=(id)image.CGI&&&&[layer setContents:(id)image.CGImage];&&&&&//添加图层到根图层&&&&[self.view.layer addSublayer:layer];}&@end
既然如此为什么还大费周章的说形变呢,因为形变对于动画有特殊的意义。在动画开发中形变往往不是直接设置transform,而是通过keyPath进行设置。这种方法设置形变的本质和前面没有区别,只是利用了KVC可以动态修改其属性值而已,但是这种方式在动画中确实很常用的,因为它可以很方便的将几种形变组合到一起使用。同样是解决动画旋转问题,只要将前面的旋转代码改为下面的代码即可:
Objective-C
[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];
[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];
当然,通过key path设置形变参数就需要了解有哪些key path可以设置,这里就不再一一列举,大家可以参照Xcode帮助文档中“CATransform3D Key Paths”一节,里面描述的很详细。
使用自定义图层绘图
在自定义图层中绘图时只要自己编写一个类继承于CALayer然后在drawInContext:中绘图即可。同前面在代理方法绘图一样,要显示图层中绘制的内容也要调用图层的setNeedDisplay方法,否则drawInContext方法将不会调用。
前面的文章中曾经说过,在使用Quartz 2D在UIView中绘制图形的本质也是绘制到图层中,为了说明这个问题下面演示自定义图层绘图时没有直接在视图控制器中调用自定义图层,而是在一个UIView将自定义图层添加到UIView的根图层中(例子中的UIView跟自定义图层绘图没有直接关系)。从下面的代码中可以看到:UIView在显示时其根图层会自动创建一个CGContextRef(CALayer本质使用的是位图上下文),同时调用图层代理(UIView创建图层会自动设置图层代理为其自身)的draw: inContext:方法并将图形上下文作为参数传递给这个方法。而在UIView的draw:inContext:方法中会调用其drawRect:方法,在drawRect:方法中使用UIGraphicsGetCurrentContext()方法得到的上下文正是前面创建的上下文。
Objective-C
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCLayer.h"
@implementation KCLayer
-(void)drawInContext:(CGContextRef)ctx{
NSLog(@"3-drawInContext:");
NSLog(@"CGContext:%@",ctx);
CGContextRotateCTM(ctx, M_PI_4);
CGContextSetRGBFillColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
CGContextSetRGBStrokeColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextFillEllipseInRect(ctx, CGRectMake(50, 50, 100, 100));
CGContextMoveToPoint(ctx, 94.5, 33.5);
//// Star Drawing
CGContextAddLineToPoint(ctx,104.02, 47.39);
CGContextAddLineToPoint(ctx,120.18, 52.16);
CGContextAddLineToPoint(ctx,109.91, 65.51);
CGContextAddLineToPoint(ctx,110.37, 82.34);
CGContextAddLineToPoint(ctx,94.5, 76.7);
CGContextAddLineToPoint(ctx,78.63, 82.34);
CGContextAddLineToPoint(ctx,79.09, 65.51);
CGContextAddLineToPoint(ctx,68.82, 52.16);
CGContextAddLineToPoint(ctx,84.98, 47.39);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathFillStroke);
1234567891011121314151617181920212223242526272829303132333435363738
////&&KCLayer.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCLayer.h"&@implementation KCLayer&-(void)drawInContext:(CGContextRef)ctx{&&&&NSLog(@"3-drawInContext:");&&&&NSLog(@"CGContext:%@",ctx);//&&&&CGContextRotateCTM(ctx, M_PI_4);&&&&CGContextSetRGBFillColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);&&&&CGContextSetRGBStrokeColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);//&&&&CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));//&&&&CGContextFillEllipseInRect(ctx, CGRectMake(50, 50, 100, 100));&&&&CGContextMoveToPoint(ctx, 94.5, 33.5);&&&&&//// Star Drawing&&&&CGContextAddLineToPoint(ctx,104.02, 47.39);&&&&CGContextAddLineToPoint(ctx,120.18, 52.16);&&&&CGContextAddLineToPoint(ctx,109.91, 65.51);&&&&CGContextAddLineToPoint(ctx,110.37, 82.34);&&&&CGContextAddLineToPoint(ctx,94.5, 76.7);&&&&CGContextAddLineToPoint(ctx,78.63, 82.34);&&&&CGContextAddLineToPoint(ctx,79.09, 65.51);&&&&CGContextAddLineToPoint(ctx,68.82, 52.16);&&&&CGContextAddLineToPoint(ctx,84.98, 47.39);&&&&CGContextClosePath(ctx);&&&&&CGContextDrawPath(ctx, kCGPathFillStroke);}&@end
Objective-C
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCView.h"
#import "KCLayer.h"
@implementation KCView
-(instancetype)initWithFrame:(CGRect)frame{
NSLog(@"initWithFrame:");
if (self=[super initWithFrame:frame]) {
KCLayer *layer=[[KCLayer alloc]init];
layer.bounds=CGRectMake(0, 0, 185, 185);
layer.position=CGPointMake(160,284);
layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGC
//显示图层
[layer setNeedsDisplay];
[self.layer addSublayer:layer];
-(void)drawRect:(CGRect)rect{
NSLog(@"2-drawRect:");
NSLog(@"CGContext:%@",UIGraphicsGetCurrentContext());//得到的当前图形上下文正是drawLayer中传递的
[super drawRect:rect];
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
NSLog(@"1-drawLayer:inContext:");
NSLog(@"CGContext:%@",ctx);
[super drawLayer:layer inContext:ctx];
1234567891011121314151617181920212223242526272829303132333435363738394041424344
////&&KCView.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCView.h"#import "KCLayer.h"&@implementation KCView&-(instancetype)initWithFrame:(CGRect)frame{&&&&NSLog(@"initWithFrame:");&&&&if (self=[super initWithFrame:frame]) {&&&&&&&&KCLayer *layer=[[KCLayer alloc]init];&&&&&&&&layer.bounds=CGRectMake(0, 0, 185, 185);&&&&&&&&layer.position=CGPointMake(160,284);&&&&&&&&layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;&&&&&&&&&//显示图层&&&&&&&&[layer setNeedsDisplay];&&&&&&&&&[self.layer addSublayer:layer];&&&&}&&&&return self;}&-(void)drawRect:(CGRect)rect{&&&&NSLog(@"2-drawRect:");&&&&NSLog(@"CGContext:%@",UIGraphicsGetCurrentContext());//得到的当前图形上下文正是drawLayer中传递的&&&&[super drawRect:rect];&}&-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{&&&&NSLog(@"1-drawLayer:inContext:");&&&&NSLog(@"CGContext:%@",ctx);&&&&[super drawLayer:layer inContext:ctx];&}&@end
KCMainViewController.m
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
#import "KCView.h"
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds];
view.backgroundColor=[UIColor colorWithRed:249.0/255.0 green:249.0/255.0 blue:249.0/255.0 alpha:1];
[self.view addSubview:view];
123456789101112131415161718192021222324252627
////&&KCMainViewController.m//&&CALayer////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"#import "KCView.h"&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds];&&&&view.backgroundColor=[UIColor colorWithRed:249.0/255.0 green:249.0/255.0 blue:249.0/255.0 alpha:1];&&&&&[self.view addSubview:view];}&@end
运行效果:
Core Animation
大家都知道在iOS中实现一个动画相当简单,只要调用UIView的块代码即可实现一个动画效果,这在其他系统开发中基本不可能实现。下面通过一个简单的UIView进行一个图片放大动画效果演示:
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
@interface KCMainViewController ()
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image=[UIImage imageNamed:@"open2.png"];
UIImageView *imageView=[[UIImageView alloc]init];
imageView.image=
imageView.frame=CGRectMake(120, 140, 80, 80);
[self.view addSubview:imageView];
//两秒后开始一个持续一分钟的动画
[UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
imageView.frame=CGRectMake(80, 100, 160, 160);
} completion:nil];
12345678910111213141516171819202122232425262728293031
////&&KCMainViewController.m//&&Animation////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"&@interface KCMainViewController ()&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&UIImage *image=[UIImage imageNamed:@"open2.png"];&&&&UIImageView *imageView=[[UIImageView alloc]init];&&&&imageView.image=image;&&&&imageView.frame=CGRectMake(120, 140, 80, 80);&&&&[self.view addSubview:imageView];&&&&&//两秒后开始一个持续一分钟的动画&&&&[UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{&&&&&&&&imageView.frame=CGRectMake(80, 100, 160, 160);&&&&} completion:nil];}@end
使用上面UIView封装的方法进行动画设置固然十分方便,但是具体动画如何实现我们是不清楚的,而且上面的代码还有一些问题是无法解决的,例如:如何控制动画的暂停?如何进行动画的组合?。。。
这里就需要了解iOS的核心动画Core Animation(包含在Quartz Core框架中)。在iOS中核心动画分为几类:基础动画、关键帧动画、动画组、转场动画。各个类的关系大致如下:
CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制,本身实现了CAMediaTiming协议。
CAPropertyAnimation:属性动画的基类(通过属性进行动画设置,注意是可动画属性),不能直接使用。
CAAnimationGroup:动画组,动画组是一种组合模式设计,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CATransition:转场动画,主要通过滤镜进行动画效果设置。
CABasicAnimation:基础动画,通过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,同样是通过属性进行动画参数控制,但是同基础动画不同的是它可以有多个状态控制。
基础动画、关键帧动画都属于属性动画,就是通过修改属性值产生动画效果,开发人员只需要设置初始值和结束值,中间的过程动画(又叫“补间动画”)由系统自动计算产生。和基础动画不同的是关键帧动画可以设置多个属性值,每两个属性中间的补间动画由系统自动完成,因此从这个角度而言基础动画又可以看成是有两个关键帧的关键帧动画。
在开发过程中很多情况下通过基础动画就可以满足开发需求,前面例子中使用的UIView代码块进行图像放大缩小的演示动画也是基础动画(在iOS7中UIView也对关键帧动画进行了封装),只是UIView装饰方法隐藏了更多的细节。如果不使用UIView封装的方法,动画创建一般分为以下几步:
1.初始化动画并设置动画属性
2.设置动画属性初始值(可以省略)、结束值以及其他动画属性
3.给图层添加动画
下面以一个移动动画为例进行演示,在这个例子中点击屏幕哪个位置落花将飞向哪里。
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
@interface KCMainViewController (){
CALayer *_
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGI
[self.view.layer addSublayer:_layer];
#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
//2.设置动画属性初始值和结束值
basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];
//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyO
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
////&&KCMainViewController.m//&&Animation////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"&@interface KCMainViewController (){&&&&CALayer *_layer;}&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&//设置背景(注意这个图片其实在根图层)&&&&UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];&&&&self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];&&&&&//自定义一个图层&&&&_layer=[[CALayer alloc]init];&&&&_layer.bounds=CGRectMake(0, 0, 10, 20);&&&&_layer.position=CGPointMake(50, 150);&&&&_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;&&&&[self.view.layer addSublayer:_layer];}&#pragma mark 移动动画-(void)translatonAnimation:(CGPoint)location{&&&&//1.创建动画并指定动画属性&&&&CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];&&&&&//2.设置动画属性初始值和结束值//&&&&basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态&&&&basicAnimation.toValue=[NSValue valueWithCGPoint:location];&&&&&//设置其他动画属性&&&&basicAnimation.duration=5.0;//动画时间5秒&&&&//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果&&&&//&&&&basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画&&&&&//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取&&&&[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];}&#pragma mark 点击事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{&&&&UITouch *touch=touches.anyObject;&&&&CGPoint location= [touch locationInView:self.view];&&&&//创建并开始动画&&&&[self translatonAnimation:location];}&@end
运行效果:
上面实现了一个基本动画效果,但是这个动画存在一个问题:动画结束后动画图层回到了原来的位置,当然是用UIView封装的方法是没有这个问题的。如何解决这个问题呢?
前面说过图层动画的本质就是将图层内部的内容转化为位图经硬件操作形成一种动画效果,其实图层本身并没有任何的变化。上面的动画中图层并没有因为动画效果而改变它的位置(对于缩放动画其大小也是不会改变的),所以动画完成之后图层还是在原来的显示位置没有任何变化,如果这个图层在一个UIView中你会发现在UIView移动过程中你要触发UIView的点击事件也只能点击原来的位置(即使它已经运动到了别的位置),因为它的位置从来没有变过。当然解决这个问题方法比较多,这里不妨在动画完成之后重新设置它的位置。
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
@interface KCMainViewController (){
CALayer *_
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGI
[self.view.layer addSublayer:_layer];
#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
//2.设置动画属性初始值和结束值
basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];
//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
basicAnimation.delegate=
//存储当前位置在动画结束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyO
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];
#pragma mark - 动画代理方法
#pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画
#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
////&&KCMainViewController.m//&&Animation////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"&@interface KCMainViewController (){&&&&CALayer *_layer;}&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&//设置背景(注意这个图片其实在根图层)&&&&UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];&&&&self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];&&&&&//自定义一个图层&&&&_layer=[[CALayer alloc]init];&&&&_layer.bounds=CGRectMake(0, 0, 10, 20);&&&&_layer.position=CGPointMake(50, 150);&&&&_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;&&&&[self.view.layer addSublayer:_layer];}&#pragma mark 移动动画-(void)translatonAnimation:(CGPoint)location{&&&&//1.创建动画并指定动画属性&&&&CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];&&&&&//2.设置动画属性初始值和结束值//&&&&basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态&&&&basicAnimation.toValue=[NSValue valueWithCGPoint:location];&&&&&//设置其他动画属性&&&&basicAnimation.duration=5.0;//动画时间5秒&&&&//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果&&&&//&&&&basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画&&&&basicAnimation.delegate=self;&&&&//存储当前位置在动画结束后使用&&&&[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];&&&&&//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取&&&&[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];}&#pragma mark 点击事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{&&&&UITouch *touch=touches.anyObject;&&&&CGPoint location= [touch locationInView:self.view];&&&&//创建并开始动画&&&&[self translatonAnimation:location];}&#pragma mark - 动画代理方法#pragma mark 动画开始-(void)animationDidStart:(CAAnimation *)anim{&&&&NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));&&&&NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画}&#pragma mark 动画结束-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{&&&&NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));&&&&_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];}@end
上面通过给动画设置一个代理去监听动画的开始和结束事件,在动画开始前给动画添加一个自定义属性“KCBasicAnimationLocation”存储动画终点位置,然后在动画结束后设置动画的位置为终点位置。
如果运行上面的代码大家可能会发现另外一个问题,那就是动画运行完成后会重新从起始点运动到终点。这个问题产生的原因就是前面提到的,对于非根图层,设置图层的可动画属性(在动画结束后重新设置了position,而position是可动画属性)会产生动画效果。解决这个问题有两种办法:关闭图层隐式动画、设置动画图层为根图层。显然这里不能采取后者,因为根图层当前已经作为动画的背景。
要关闭隐式动画需要用到动画事务CATransaction,在事务内将隐式动画关闭,例如上面的代码可以改为:
Objective-C
#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];
_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
//提交事务
[CATransaction commit];
12345678910111213
#pragma mark 动画结束-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{&&&&NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));&&&&//开启事务&&&&[CATransaction begin];&&&&//禁用隐式动画&&&&[CATransaction setDisableActions:YES];&&&&&_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];&&&&&//提交事务&&&&[CATransaction commit];}
上面通过在animationDidStop中重新设置动画的位置主要为了说明隐式动画关闭和动画事件之间传参的内容,有朋友发现这种方式有可能在动画运行完之后出现从原点瞬间回到终点的过程,最早在调试的时候没有发现这个问题,这里感谢这位朋友。其实解决这个问题并不难,首先必须设置fromValue,其次在动画开始前设置动画position为终点位置(当然也必须关闭隐式动画)。但是这里主要还是出于学习的目的,真正开发的时候做平移动画直接使用隐式动画即可,没有必要那么麻烦。
当然上面的动画还显得有些生硬,因为落花飘散的时候可能不仅仅是自由落体运动,本身由于空气阻力、外界风力还会造成落花在空中的旋转、摇摆等,这里不妨给图层添加一个旋转的动画。对于图层的旋转前面已经演示过怎么通过key path设置图层旋转的内容了,在这里需要强调一下,图层的形变都是基于锚点进行的。例如旋转,旋转的中心点就是图层的锚点。
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
@interface KCMainViewController (){
CALayer *_
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];
//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.anchorPoint=CGPointMake(0.5, 0.6);//设置锚点
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGI
[self.view.layer addSublayer:_layer];
#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
//2.设置动画属性初始值、结束值
basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];
//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
basicAnimation.delegate=
//存储当前位置在动画结束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];
//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该图层时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
#pragma mark 旋转动画
-(void)rotationAnimation{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
//2.设置动画属性初始值、结束值
basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
//设置其他动画属性
basicAnimation.duration=6.0;
basicAnimation.autoreverses=//旋转后再旋转到原来的位置
//4.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyO
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];
[self rotationAnimation];
#pragma mark - 动画代理方法
#pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画
#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];
_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
//提交事务
[CATransaction commit];
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
////&&KCMainViewController.m//&&Animation////&&Created by Kenshin Cui on 14-3-22.//&&Copyright (c) 2014年 Kenshin Cui. All rights reserved.//&#import "KCMainViewController.h"&@interface KCMainViewController (){&&&&CALayer *_layer;}&@end&@implementation KCMainViewController&- (void)viewDidLoad {&&&&[super viewDidLoad];&&&&&//设置背景(注意这个图片其实在根图层)&&&&UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];&&&&self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];&&&&&//自定义一个图层&&&&_layer=[[CALayer alloc]init];&&&&_layer.bounds=CGRectMake(0, 0, 10, 20);&&&&_layer.position=CGPointMake(50, 150);&&&&_layer.anchorPoint=CGPointMake(0.5, 0.6);//设置锚点&&&&_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;&&&&[self.view.layer addSublayer:_layer];}&#pragma mark 移动动画-(void)translatonAnimation:(CGPoint)location{&&&&//1.创建动画并指定动画属性&&&&CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];&&&&&//2.设置动画属性初始值、结束值//&&&&basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态&&&&basicAnimation.toValue=[NSValue valueWithCGPoint:location];&&&&&//设置其他动画属性&&&&basicAnimation.duration=5.0;//动画时间5秒&&&&//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果&&&&//&&&&basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画&&&&basicAnimation.delegate=self;&&&&//存储当前位置在动画结束后使用&&&&[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];&&&&&//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该图层时可以使用此名称获取&&&&[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];}&#pragma mark 旋转动画-(void)rotationAnimation{&&&&//1.创建动画并指定动画属性&&&&CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];&&&&&//2.设置动画属性初始值、结束值//&&&&basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];&&&&basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];&&&&&//设置其他动画属性&&&&basicAnimation.duration=6.0;&&&&basicAnimation.autoreverses=true;//旋转后再旋转到原来的位置&&&&&//4.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取&&&&[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];}&#pragma mark 点击事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{&&&&UITouch *touch=touches.anyObject;&&&&CGPoint location= [touch locationInView:self.view];&&&&//创建并开始动画&&&&[self translatonAnimation:location];&&&&&[self rotationAnimation];}&#pragma mark - 动画代理方法#pragma mark 动画开始-(void)animationDidStart:(CAAnimation *)anim{&&&&NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));&&&&NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画}&#pragma mark 动画结束-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{&&&&NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));&&&&//开启事务&&&&[CATransaction begin];&&&&//禁用隐式动画&&&&[CATransaction setDisableActions:YES];&&&&&_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];&&&&&//提交事务&&&&[CATransaction commit];}&@end
上面代码中结合两种动画操作,需要注意的是只给移动动画设置了代理,在旋转动画中并没有设置代理,否则代理方法会执行两遍。由于旋转动画会无限循环执行(上面设置了重复次数无穷大),并且两个动画的执行时间没有必然的关系,这样一来移动停止后可能还在旋转,为了让移动动画停止后旋转动画停止就需要使用到动画的暂停和恢复方法。
核心动画的运行有一个媒体时间的概念,假设将一个旋转动画设置旋转一周用时60秒的话,那么当动画旋转90度后媒体时间就是15秒。如果此时要将动画暂停只需要让媒体时间偏移量设置为15秒即可,并把动画运行速度设置为0使其停止运动。类似的,如果又过了60秒后需要恢复动画(此时媒体时间为75秒),这时只要将动画开始开始时间设置为当前媒体时间75秒减去暂停时的时间(也就是之前定格动画时的偏移量)15秒(开始时间=75-15=60秒),那么动画就会重新计算60秒后的状态再开始运行,与此同时将偏移量重新设置为0并且把运行速度设置1。这个过程中真正起到暂停动画和恢复动画的其实是动画速度的调整,媒体时间偏移量以及恢复时的开始时间设置主要为了让动画更加连贯。
下面的代码演示了移动动画结束后旋转动画暂停,并且当再次点击动画时旋转恢复的过程(注意在移动过程中如果再次点击屏幕可以暂停移动和旋转动画,再次点击可以恢复两种动画。但是当移动结束后触发了移动动画的完成事件如果再次点击屏幕则只能恢复旋转动画,因为此时移动动画已经结束而不是暂停,无法再恢复)。
Objective-C
KCMainViewController.m
Created by Kenshin Cui on 14-3-22.
Copyright (c) 2014年 Kenshin Cui. All rights reserved.
#import "KCMainViewController.h"
@interface KCMainViewController (){
CALayer *_
@implementation KCMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
//设置背景(注意这个图片其实在根图层)
UIImage *backgroundIma

我要回帖

更多关于 内存价格持续上升 的文章

 

随机推荐