第十一章:图形、图像与动画

首先,掌握图形、图像处理以及动画知识可以实现自定义UIView,并在UIView上绘制几何图形、文本、位图、曲线,以及使用路径绘制等。其次,iOS还提供了Core Animation来实现动画,以这种方式编程更简单,而且通过Core Animation来是吸纳动画具有很好的性能。

一、使用UIImage和CGImage处理位图

1、显示动画

UIImage专门用于显示各种位图,该类支持如下的各种图片格式:

这里写图片描述

  当系统内存紧张时,UIImage会将图片数据从UIImage对象中清理出去来以节省系统内存,这里的清理行为只是清理UIImage内部存储的图片数据,并不清理UIImage对象本身。当程序使用一个图片数据被清理过的UIImage对象时,该UIImage将会自动从原始的图片文件中加载图片数据。

如果使用UIImage来显示静止的图片,UIImage提供了如下常用的方法:

  • (1)、+ (UIImage )imageNamed:(NSString )name;该方法用于加载指定文件名对应的图片。该方法有缓存机制,如果该方法试图加载的文件不存在,它才会去加载图片文件并缓存它;如果系统已经缓存过指定的图片,该方法将直接使用已缓存的图片。

    提示:当程序需要频繁的加载,卸载图片文件时,不应该使用该方法来加载图片;
    
  • (2)、+ (UIImage )imageWithContentsOfFile:(NSString )path;该方法用于加载指定文件名对应的图片;

  • (3)、+ (UIImage )imageWithData:(NSData )data;该方法用于根据NSData中封装的图片数据来创建图片;
  • (4)、+ (UIImage )imageWithData:(NSData )data scale:(CGFloat)scale;该方法用于根据NSData中封装的图片数据来创建图片,并按指定的缩放因子对图片进行缩放;该比例为image.size.width/SCREEN_WIDTH;
  • (5)、+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage;该方法用于根据指定的CGImageRef对象来创建UIImage;
  • (6)、+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation;该方法用于根据指定的CGImageRef对象来创建UIImage,并将图片缩放到指定比例。该方法的最后一个参数指定对图片执行旋转、镜像等变换操作。

对UIImage需要读取的图片进行分类:

  • (1)、从应用资源中读取图片:
    如果程序需要从应用资源中读取图片,可借助UIImage的imageNamed:方法进行读取。例如:

    UIImage* image = [UIImage imageNamed:@"1.png"];
    
  • (2)、从网络读取图片:
    如果需要从网络读取图片,可借助NSData的方法来加载指定URL对应的网络数据,然后利用UIImage的imageWithData:方法来加载图片即可。例如:

    NSURL* url = [NSURL URLWithString:@"http://www.crazy.org/logo.jpg"];
    NSData* data = [NSData dataWithContentsOfURL:url];
    UIImage* image = [UIImage imageWithData:data];
    
  • (3)、从手机本地读取图片:
    如果需要读取手机本地的图片,只要先获取图片路径,在利用UIImage的imageWithContentsOfFile:方法加载图片即可。例如:

    NSString* path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingFormat:@"fkjava.png"];
    UIImage* image = [UIImage imageWithContentsOfFile:path];
    

除此之外,UIImage还可以加载更多张图片,并按指定时间间隔一次显示多张图片,这就可以非常方便的实现动画效果。UIImage提供了如下方法加载多张图片实现动画。

  • (1)、+ (UIImage )animatedImageNamed:(NSString )name duration:(NSTimeInterval)duration;根据指定的图片名来加载系列图片。例如,调用该方法时的第一个参数名为butterfly,该方法将自动加载butterfly0.png、butterfly1.png、butterfly2.png等图片。
  • (2)、+ (UIImage )animatedImageWithImages:(NSArray )images duration:(NSTimeInterval)duration;该方法需要传入一个NSArray作为多张动画图片。该NSArray中的每个元素都是UIImage对象。

实例:蝴蝶飞舞

#import "RootViewController.h"
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface RootViewController (){
    UIImageView* iv;//定义显示图片的UIImageView控件
    NSTimer* timer;//定义定时器
    int x;
    int y;
}
@end
@implementation RootViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    x = 5;
    y = 4;
    self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"back2.jpg"]];
    iv = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 60, 48)];
    //数组
    NSMutableArray *images = [[NSMutableArray alloc]init];
    for(int i = 0 ;i < 18;i++){
        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"bird%d.png",i]];
        [images addObject:image];
    }
    iv.image = [UIImage animatedImageWithImages:images duration:0.4];
    [self.view addSubview:iv];
    //启动NSTimer定时器来改变UIImageView的位置
    timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(changePos) userInfo:nil repeats:YES];
}
- (void)changePos{
    CGPoint curPos = iv.center;
    //当curPos的x坐标已经超过屏幕的宽度
    if (curPos.x < 30||curPos.x > SCREEN_WIDTH - 30) {
        iv.transform = CGAffineTransformScale(iv.transform, -1, 1);
        x = -x;
    }
    if (curPos.y < 24||curPos.y > SCREEN_HEIGHT - 30) {
        y = -y;
    }
    iv.center = CGPointMake(curPos.x + x, curPos.y + y);
}
@end

2、CGImage与CGImageRef

Quartz 2D的CGImageRef实现对图片进行旋转、缩放等。

UIImage与CGImageRef之间可以相互转换,CGImageRef并不是面向对象的API,也不是类,只是一个指针类型,Quartz 2D对CGImageRef的定义为:typedef struct CGImage* CGImageRef;

获取CGImageRef,例如:

CGImageRef* ciRef = image.CGImage;

二、Quartz 2D绘图

Quartz 2D绘图的核心API是CGContextRef,该API专门用于绘制各种图形;

1、Quartz 2D绘图基础:CGContextRef

步骤:

  1. 获取绘图上下文;
  2. 创建并设置路径;
  3. 将路径添加到上下文;
  4. 设置上下文状态;
  5. 绘制路径;
  6. 释放路径。

使用Quartz 2D绘图的关键有两步:

  • ①、获取CGContextRet;
  • ②、调用CGContextRef的方法进行绘图。

(1)、自定义UIView时获取CGContextRef

开发自定义UIView的方法是,开发一个继承UIView的子类,并重写该UIView的drawRect:方法,当该UIView每次显示出来时,或该UIView的内容需要更新时,系统都会自动调用该UIView的drawRect:方法。在调用drawRect:方法之前,系统会自动配置绘图环境,因此程序只要通过如下函数即可获取CGContextRef绘图API:

CGContextRef contextRef = UIGraphicsGetCurrentContext();

提示:重写UIView的drawRect:方法绘图时,它的绘图API的坐标原点位于该控件的左上角,横向为X轴,X坐标越大,位置越向右;纵向为Y轴,Y坐标越大,位置越向下。

(2)、创建位图时获取CGContextRef

如果需要在创建位图时获取CGContextRef,那么程序需要先调用UIGraphicsBeginImageContext()函数来创建内存中的图片。然后才能调用UIGraphicsGetCurrentContext()获取绘图的CGContextRef。

  • ①、创建内存中的图片

    UIGraphicsBeginImageContext(CGSizeMake(320, 480));
    
  • ②、获取向内存中图片执行绘图的CGContextRef

    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    

Quartz 2D提供的绘图函数如下:

  • (1)、void CGContextClearRect(CGContextRef c, CGRect rect);擦除指定矩形区域上绘制的图像;
  • (2)、void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);使用指定模式绘制当前CGContextRef中所包含的路径。第二个参数支持kCGPathFill、kCGPathEOFill、kCGPathStroke、kCGPathFillStroke、kCGPathEOFillStroke等枚举值;
  • (3)、void CGContextEOFillPath(CGContextRef c);使用奇偶规则来填充该路径包围的区域。奇偶规则指:如果某个点被路径包围了奇数次,系统绘制该点;如果被包围了偶数次,系统不绘制该点填充该路径包围的区域;
  • (4)、void CGContextFillPath(CGContextRef c);填充该路径包围的区域;
  • (5)、void CGContextFillRect(CGContextRef c, CGRect rect);填充rect代表的矩形;
  • (6)、void CGContextFillRects(CGContextRef c, const CGRect rects[], size_t count);填充多个矩形;
  • (7)、void CGContextFillEllipseInRect(CGContextRef context, CGRect rect);填充rect矩形的内切椭圆区域;
  • (8)、void CGContextStrokePath(CGContextRef c);使用当前CGContextRef设置的线宽绘制路径;
  • (9)、void CGContextStrokeRect(CGContextRef c, CGRect rect);使用当前CGContextRef设置的线宽绘制矩形框;
  • (10)、void CGContextStrokeRectWithWidth(CGContextRef c, CGRect rect, CGFloat width);使用指定线宽绘制矩形框;
  • (11)、void CGContextReplacePathWithStrokedPath(CGContextRef c);使用绘制当前路径时覆盖的区域作为当前CGContextRef中的新路径。举例来说,假如当前CGContextRef包含一个圆形路径且线宽为10,调用该方法后,当前CGContextRef将包含一个环宽为10的环形路径;
  • (12)、void CGContextStrokeEllipseInRect(CGContextRef context, CGRect rect);使用当前CGContextRef设置的线宽绘制rect矩形的内切椭圆;
  • (13)、void CGContextStrokeLineSegments(CGContextRef c, const CGPoint points[], size_t count);使用当前CGContextRef设置的线宽绘制多条线段。该方法需要传入2N个CGPoint组成的数组,其中1、2个点组成第一条线段,3、4个点组成第二条线段,以此类推。

Quartz 2D提供的路径函数如下:CGPathRef代表任意多条直线或曲线连接成的任意图形。
在绘图之前,还需要对绘图的颜色、线条粗细等属性进行设置。

  • (1)、void CGContextSaveGState(CGContextRef c);保存CGContextRef当前的绘图状态,方便以后恢复该状态;
  • (2)、void CGContextRestoreGState(CGContextRef c);把CGContextRef的状态恢复到最近一次保存时的状态;
  • (3)、CGInterpolationQuality CGContextGetInterpolationQuality(CGContextRef context);获取当前CGContextRef在放大图片时的插值质量;
  • (4)、void CGContextSetInterpolationQuality(CGContextRef context, CGInterpolationQuality quality);设置当前CGContextRef在放大图片时的插值质量;
  • (5)、void CGContextSetLineCap(CGContextRef c, CGLineCap cap);设置线段端点的绘制形状。该属性支持如下三个值:
    • ①、kCGLineCapButt:该属性值指定不绘制端点,线条结尾处直接结束。这是默认值;
    • ②、kCGLineCapRound:该属性值指定绘图圆形端点,线条结尾处绘制一个直径为线条宽度的半圆;
    • ③、kCGLineCapSquare:该属性值指定绘制方形端点。线条结尾处绘制半个边长为线条宽度的正方形。需要说明的是,这个形状的端点与”butt“形状的端点十分相似。只是采用这种方式的端点的线条略长一点而已。
  • (6)、void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat lengths[], size_t count);设置绘制边框时所用的点线模式,Quartz 2D支持非常强大的点线模式。
  • (7)、void CGContextSetLineJoin(CGContextRef c, CGLineJoin join);设置线条连接点的风格,该属性支持如下三个值:
    • ①、kCGLineJoinMeter:这是默认的属性值、该方格的连接点形状如箭头;
    • ②、kCGLineJoinRound:该方格的连接点形状如圆形箭头;
    • ③、kCGLineJoinBevel:该方格的连接点形状如削平箭头;
  • (8)、void CGContextSetLineWidth(CGContextRef c, CGFloat width);设置绘制直线、边框时的线条宽度;
  • (9)、void CGContextSetMiterLimit(CGContextRef c, CGFloat limit);把连接点风格设置meter风格时,该方法用于控制锐角箭头的长度;
  • (10)、void CGContextSetPatternPhase(CGContextRef context, CGSize phase);设置该CGContextRef采用位图填充的相位;
  • (11)、void CGContextSetFillPattern(CGContextRef context, CGPatternRef pattern, const CGFloat components[]);设置该CGContextRef使用位图填充;
  • (12)、void CGContextSetShouldAntialias(CGContextRef context, bool shouldAntialias);设置该CGContextRef是否应该抗锯齿(即光滑圆形曲线边缘);
  • (13)、void CGContextSetStrokePattern(CGContextRef context, CGPatternRef pattern, const CGFloat components[]);设置该CGContextRef使用位图绘制线条、边框;
  • (14)、void CGContextSetBlendMode(CGContextRef context, CGBlendMode mode);设置CGContextRef的叠加模式。Quartz 2D支持多种叠加模式;
  • (15)、void CGContextSetAllowsAntialiasing(CGContextRef context, bool allowsAntialiasing);设置该CGContext是否允许抗锯齿;
  • (16)、void CGContextSetAllowsFontSmoothing(CGContextRef context, bool allowsFontSmoothing);设置该CGContext是否允许光滑字体;
  • (17)、void CGContextSetShouldSmoothFonts(CGContextRef context, bool shouldSmoothFonts);设置该CGContext是否允许光滑字体;
  • (18)、void CGContextSetAlpha(CGContextRef c, CGFloat alpha);设置全局透明度;
  • (19)、void CGContextSetCMYKFillColor(CGContextRef context, CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha);使用CMYK颜色模式来设置该CGContextRef的填充颜色;
  • (20)、void CGContextSetCMYKStrokeColor(CGContextRef context, CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha);使用CMYK颜色模式来设置该CGContextRef的线条颜色;
  • (21)、void CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color);使用指定颜色来设置该CGContextRef的填充颜色;
  • (22)、void CGContextSetStrokeColorWithColor(CGContextRef c, CGColorRef color);使用指定颜色来设置该CGContextRef的线条颜色;
  • (23)、void CGContextSetGrayFillColor(CGContextRef context, CGFloat gray, CGFloat alpha);使用灰色颜色设置该CGContextRef的填充颜色;
  • (24)、void CGContextSetGrayStrokeColor(CGContextRef context, CGFloat gray, CGFloat alpha);使用灰色颜色设置该CGContextRef的线条颜色;
  • (25)、void CGContextSetRGBFillColor(CGContextRef context, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha);使用RGB颜色模式来设置该CGContextRef的填充颜色;
  • (26)、void CGContextSetRGBStrokeColor(CGContextRef context, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha);使用RGB颜色模式来设置该CGContextRef的线条颜色;
  • (27)、void CGContextSetShadow(CGContextRef context, CGSize offset, CGFloat blur);设置阴影在X、Y方向上的偏移,以及模糊度(blur值越大,阴影越模糊)。该函数没有设置阴影的颜色,默认使用1/3透明的黑色(即RGBA{0,0,0,1/3})作为阴影的颜色;
  • (28)、void CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color);设置阴影在X、Y方向上的偏移,以及模糊度和阴影的颜色;

2、绘制几何图形

例如:

这里写图片描述

- (void)drawRect:(CGRect)rect{
    //获取绘图上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //设置线宽
    CGContextSetLineWidth(ctx, 16);
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);
     1、①、
    /*--- 下面绘制3个线段测试端点形状 ---*/
    //定义4个点,绘制线段
    const CGPoint points1[] = {CGPointMake(10, 20),CGPointMake(100, 20),CGPointMake(100, 20),CGPointMake(20, 50)};
    //绘制线段(默认不绘制端点)
    CGContextStrokeLineSegments(ctx, points1, 4);
    //设置线段的端点形状:方形端点
    CGContextSetLineCap(ctx, kCGLineCapSquare);
    ②、
    //定义4个点,绘制线段
    const CGPoint points2[] = {CGPointMake(110, 20),CGPointMake(200, 20),CGPointMake(200, 20),CGPointMake(120, 50)};
    //绘制线段
    CGContextStrokeLineSegments(ctx, points2, 4);
    //设置线段的端点形状:圆形端点
    CGContextSetLineCap(ctx, kCGLineCapRound);
    ③、
    //定义4个点,绘制线段
    const CGPoint points3[] = {CGPointMake(220, 20),CGPointMake(300, 20),CGPointMake(300, 20),CGPointMake(220, 50)};
    //绘制线段
    CGContextStrokeLineSegments(ctx, points3, 4);
    //设置线段的端点形状
    CGContextSetLineCap(ctx, kCGLineCapButt);
     2/*--- 下面绘制3个线段测试点线模式 ---*/
    //设置线宽
     ①、
    CGContextSetLineWidth(ctx, 10);
    CGFloat patterns1[] = {6,10};
    //设置点线模式:实线宽6,间距宽10
    CGContextSetLineDash(ctx, 0, patterns1, 1);
    //定义两个点,绘制线段
    const CGPoint points4[] = {CGPointMake(40, 65),CGPointMake(280, 65)};
    //绘制线段
    CGContextStrokeLineSegments(ctx, points4, 2);
     ②、
    //设置点线模式:实线宽6,宽间距10,但第一个实线宽为3
    CGContextSetLineDash(ctx, 3, patterns1, 1);
    //定义两个点,绘制线段
    const CGPoint points5[] = {CGPointMake(40, 85),CGPointMake(280, 85)};
    //绘制线段
    CGContextStrokeLineSegments(ctx, points5, 2);
    ③、
    CGFloat patterns2[] = {5,1,4,1,3,1,2,1,1,1,1,2,1,3,1,4,1,5};
    //设置点线模式:实线宽6,宽间距10,但第一个实线宽为3
    CGContextSetLineDash(ctx, 0, patterns2, 18);
    //定义两个点,绘制线段
    const CGPoint points6[] = {CGPointMake(40, 105),CGPointMake(280, 105)};
    //绘制线段
    CGContextStrokeLineSegments(ctx, points6, 2);
    3/*--- 下面填充矩形 —*/
     ①、
    //设置线条颜色
    CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
    //设置线条宽度
    CGContextSetLineWidth(ctx, 14);
    //设置填充颜色
    CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
    //填充一个矩形
    CGContextFillRect(ctx, CGRectMake(30, 120, 120, 60));
    ②、
    //设置填充颜色
    CGContextSetFillColorWithColor(ctx, [UIColor yellowColor].CGColor);
    //填充一个矩形
    CGContextFillRect(ctx, CGRectMake(80, 160, 120, 60));
    4/*--- 下面绘制矩形边框 —*/
    ①、
    //取消设置点线模式
    CGContextSetLineDash(ctx, 0, 0, 0);
    //绘制一个矩形边框
    CGContextStrokeRect(ctx, CGRectMake(30, 230, 120, 60));
    //设置线条颜色
    CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
    //设置线条连接点的形状
    CGContextSetLineJoin(ctx, kCGLineJoinRound);
    ②、
    //绘制一个矩形边框
    CGContextStrokeRect(ctx, CGRectMake(80, 260, 120, 60));
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 1.0, 0, 1.0, 1.0);
    //设置线条连接点的形状
    CGContextSetLineJoin(ctx, kCGLineJoinBevel);
    ③、
    //绘制一个矩形边框
    CGContextStrokeRect(ctx, CGRectMake(130, 290, 120, 60));
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 0, 1, 1, 1);
    //设置线条连接点的形状
    CGContextSetLineJoin(ctx, kCGLineJoinMiter);
     ④、
    //绘制一个椭圆
    CGContextStrokeEllipseInRect(ctx, CGRectMake(30, 380, 120, 60));
     ⑤、
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //填充一个椭圆
    CGContextFillEllipseInRect(ctx, CGRectMake(180, 380, 120, 60));
}

3、点线模式

使用Quartz 2D绘制线段或边框时,默认总是使用实线。如果希望使用点线进行绘制,可以使用CGContextRef的CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat lengths[], size_t count)函数进行设置,该函数的第三个参数是点线模式的关键,该参数是一个CGFloat型数组(第4个参数通常用于指定该数组的长度),每个CGFloat值一次控制点线的实现长度、间距。比如该参数如下:

  • (1)、{2,3}:代表长为2的实线、距离为3的间距、长为2的实线、距离为3的间距…这种点线模式;
  • (2)、{2,3,1}:代表长为2的实线、距离为3的间距、长为1的实线、距离为2的间距、长为3的实线、距离为1的间距…这种点线模式;
  • (3)、{5,3,1,2}:代表长为5的实线、距离为3的间距、长为1的实线、距离为2的间距、长为5的实线、距离为3的间距、长为1的实线、距离为2的间距…这种点线模式;

    该方法的第2个参数用于指定点线的相位,该参数将会与第3个参数协同起作用,比如如下参数组合:

  • (1)、phase = 1,lengths = {2,3}:代表长为2的实线、距离为3的间距、长为2的实线、距离为3的间距。但开始绘制起点时只绘制长度为1的实线,因为phase为1就是控制该点线“移过”1个点;
  • (2)、phase = 3,lengths = {5,3,1,2}:代表长为5的实线、距离为3的间距、长为1的实线、距离为2的间距、长为5的实线、距离为3的间距、长为1的实线、距离为2的间距。但开始绘制时只绘制长度为2的实线,因为phase为3就是控制该点线“移过”3个点。

4、绘制文本

CGContextRef为绘制文本提供了如下函数:

  • (1)、CGAffineTransform CGContextGetTextMatrix(CGContextRef c);获取当前对文本执行变换的变换矩阵;
  • (2)、CGPoint CGContextGetTextPosition(CGContextRef context);获取该CGContextRef中当前绘制文本的位置;
  • (3)、void CGContextSelectFont(CGContextRef c, const char *name,CGFloat size, CGTextEncoding textEncoding);设置该CGContextRef当前绘制文本的字体、字体大小;
  • (4)、void CGContextSetCharacterSpacing(CGContextRef context,CGFloat spacing);设置该CGContextRef中绘制文本的字符串间距;
  • (5)、void CGContextSetFont(CGContextRef c, CGFontRef font);设置该CGContextRef中绘制文本的字体;
  • (6)、void CGContextSetFontSize(CGContextRef c, CGFloat size);设置该CGContextRef中绘制文本的字体大小;
  • (7)、void CGContextSetTextDrawingMode(CGContextRef c, CGTextDrawingMode mode);绘制文本的绘制模式。该函数支持kCGTextFill、kCGTextStroke、kCGTextFillStroke等绘制模式;
  • (8)、void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t);设置对将要绘制的文本执行指定的变换;
  • (9)、void CGContextSetTextPosition(CGContextRef c, CGFloat x, CGFloat y);设置CGContextRef的文本的绘制位置
  • (10)、void CGContextShowText(CGContextRef c, const char *string, size_t length);控制CGContextRef在当前绘制点绘制指定文本;
  • (11)、void CGContextShowTextAtPoint(CGContextRef c, CGFloat x, CGFloat y, const char *string, size_t length);控制CGContextRef在指定绘制点绘制指定文本;
  • (12)、void CGContextShowTextAtPoint(CGContextRef c, CGFloat x, const char *string, size_t length):控制CGContextRef在指定绘制点绘制指定文本。

    另外:系统还为NSString提供了一个UIStringDrawing分类,通过该分类为NSString增加了drawAtPoint:withAttributes:、drawInRect:withAttributes:等绘制方法,通过这些绘制方法可以更方便的绘制字符串;
    

使用CGContextRef绘制文本的步骤如下:

  1. 获取绘图的CGContextRef;
  2. 设置绘制文本的相关属性,例如,绘制文本所用的绘制方法、字体大小、字体名称等;
  3. 如果只是绘制不需要进行变换的文本,直接调用NSString的drawAtPoint:withAttributes:、drawInAttributes:withFont:等方法绘制即可。如果需要对绘制的文本进行变换,则需要先调用CGContextSetTextMatrix()函数设置变换矩阵,在调用CGContextShowTextAtPoint()方法绘制文本。

例如:
这里写图片描述

- (void)setScaleRate:(CGFloat)scaleRate{
    if (_scaleRate != scaleRate) {
        _scaleRate = scaleRate;
        //通知该控件重绘自己
        [self setNeedsDisplay];
    }
}
- (void)setRotateAngle:(CGFloat)rotateAngle{
    if (_rotateAngle != rotateAngle) {
        _rotateAngle = rotateAngle;
        //通知该控件重绘自己
        [self setNeedsDisplay];
    }
}
//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    //获取该控件的绘图CGContextRef
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //设置字符间距
    CGContextSetCharacterSpacing(ctx, 4);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //①、设置使用填充模式绘制文字
    CGContextSetTextDrawingMode(ctx, kCGTextFill);
    //绘制文字
    [@"疯狂iOS讲义" drawAtPoint:CGPointMake(10, 20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@"Arial Rounded MT Bold" size:45],NSFontAttributeName,[UIColor magentaColor],NSForegroundColorAttributeName, nil]];
    //②、设置使用描边模式绘制文字
    CGContextSetTextDrawingMode(ctx, kCGTextStroke);
    //绘制文字
    [@"疯狂android讲义" drawAtPoint:CGPointMake(10, 80) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@"Heiti SC" size:40],NSFontAttributeName,[UIColor blueColor],NSForegroundColorAttributeName, nil]];
    //③、设置使用填充、描边模式绘制文字
    CGContextSetTextDrawingMode(ctx, kCGTextFillStroke);
    //绘制文字
    [@"疯狂Ajax讲义" drawAtPoint:CGPointMake(10, 140) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@"Heiti SC" size:50],NSFontAttributeName,[UIColor magentaColor],NSForegroundColorAttributeName, nil]];
    //定义一个垂直镜像的变换矩阵
    CGAffineTransform yRevert = CGAffineTransformMake(1, 0, 0, -1, 0, 0);
    //设置绘制文字的字体和字体颜色
//    CGContextSelectFont(ctx, "Couryer New", 40, kCGEncodingMacRoman);
    //为yRevert变换矩阵根据scaleRate添加缩放变换矩阵
    CGAffineTransform scale = CGAffineTransformScale(yRevert, self.scaleRate, self.scaleRate);
    //为scale变换矩阵添加rotateAngle添加旋转变换矩阵
    CGAffineTransform rotate = CGAffineTransformRotate(scale, M_PI * self.rotateAngle/100);
    //对CGContextRef绘制文字时应用变换
    CGContextSetTextMatrix(ctx, rotate);
    //绘制文字
    CGContextShowTextAtPoint(ctx, 50, 300, "crazyit.org", 11);
}

5、设置阴影

这里写图片描述

前面介绍了CGContextRef为设置图形阴影提供了如下两个函数:

  • (1)、void CGContextSetShadow(CGContextRef context, CGSize offset, CGFloat blur);设置阴影在X、Y方向上的偏移,以及模糊度(blur值越大,阴影越模糊)。该函数没有设置阴影的颜色,默认使用1/3透明的黑色(即RGBA{0,0,0,1/3})作为阴影的颜色;该函数的offset包含两个CGFloat值,第一个CGFloat值控制阴影在X方向的偏移,如果该值为正,则向右偏移,否则向左偏移;第二个CGFloat值控制阴影在Y方向的偏移,如果该值为正,则向下偏移,否则向上偏移。最后一个blur参数控制阴影的模糊程度,如果blur参数为1,表明阴影几乎不模糊,blur参数越大,阴影越模糊。
  • (2)、void CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color);设置阴影在X、Y方向上的偏移,以及模糊度和阴影的颜色;

例如:

//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    //获取该控件的绘图CGContextRef
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //使用默认的阴影颜色,阴影向左上角投影,模糊度为5
    CGContextSetShadow(ctx, CGSizeMake(8, -6), 5);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 1);
    //①、设置使用填充模式绘制文字
    CGContextSetTextDrawingMode(ctx, kCGTextFill);
    //绘制文字
    [@"疯狂iOS讲义" drawAtPoint:CGPointMake(10, 20) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@"Arial Rounded MT Bold" size:45],NSFontAttributeName,[UIColor magentaColor],NSForegroundColorAttributeName, nil]];
    //②、设置使用描边模式绘制文字
    CGContextSetTextDrawingMode(ctx, kCGTextStroke);
    //绘制文字
    [@"疯狂Android讲义" drawAtPoint:CGPointMake(10, 80) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:@"Heiti SC" size:40],NSFontAttributeName,[UIColor blueColor],NSForegroundColorAttributeName, nil]];

    //使用默认的阴影颜色,阴影向右下角投影,模糊度为20
    CGContextSetShadowWithColor(ctx, CGSizeMake(10, 8), 20, [[UIColor redColor] CGColor]);
    CGContextFillRect(ctx, CGRectMake(20, 150, 180, 80));
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 1, 0, 1, 1);
    CGContextStrokeRect(ctx, CGRectMake(30, 260, 180, 80));
}

6、使用路径

为了绘制更复杂的图形,必须启用路径:CGContext.h

  • (1)、void CGContextBeginPath(CGContextRef c);开始定义路径;
  • (2)、void CGContextClosePath(CGContextRef c);关闭前面定义路径;
  • (3)、void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise);想CGContextRef的当前路径上添加一段弧;
  • (4)、void CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius);向CGContextRef的当前路径上添加一段弧。与前一个方法只是定义弧的方式不同;
  • (5)、void CGContextAddCurveToPoint(CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y);向CGContextRef的当前路径上添加一段贝塞尔弧线;
  • (6)、void CGContextAddLines(CGContextRef c, const CGPoint points[], size_t count);向CGContextRef的当前路径上添加多条线段。该方法需要传入N个CGPoint组成的数组,其中1、2个点组成第一条线段,2、3点组成第2条线段,依此类推;
  • (7)、void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y);把CGContextRef的当前路径从当前结束点连接到x、y对应的点;
  • (8)、void CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y);向CGContextRef的当前路径上添加一段二次曲线;
  • (9)、void CGContextAddRect(CGContextRef c, CGRect rect);向CGContextRef的当前路径上添加一个矩形;
  • (10)、void CGContextAddRects(CGContextRef c, const CGRect rects[], size_t count);向CGContextRef的当前路径上添加多个矩形;
  • (11)、void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y);把CGContextRef的当前路径的结束点移动到x、y对应的点;
  • (12)、void CGContextAddEllipseInRect(CGContextRef context, CGRect rect);向CGContextRef的当前路径上添加一个椭圆;
  • (13)、CGPathRef CGContextCopyPath(CGContextRef context);复制当前CGContextRef包含的路径,该函数返回的CGPathRef代表当前CGContextRef包含的路径;
  • (14)、void CGContextAddPath(CGContextRef context, CGPathRef path);将已有的CGPathRef代表的路径添加到当前CGContextRef的路径中;
    除此之外,Quartz 2D该提供了如下函数来获取当前的CGContextRef所包含的路径信息:
  • (15)、bool CGContextIsPathEmpty(CGContextRef context);该函数用于判断指定CGContextRef包含的路径是否为空;
  • (16)、CGPoint CGContextGetPathCurrentPoint(CGContextRef context);该函数用于返回指定CGContextRef包含的路径的当前点;
  • (17)、CGRect CGContextGetPathBoundingBox(CGContextRef context);该函数用于返回指定CGContextRef中能完整包围所有的最小矩形;
  • (18)、bool CGContextPathContainsPoint(CGContextRef context, CGPoint point, CGPathDrawingMode mode);该函数判断指定CGContextRef包含的路径按指定绘制模式进行绘制时,是否需要绘制point点;

为了在Canvas中使用路径,可按如下步骤进行:

  1. 调用CGContextBeginPath()函数开始定义路径;
  2. 调用各种函数添加子路径;
  3. 如果路径添加完成,调用CGContextClosePath()函数关闭路径;
  4. 调用CGContextDrawPath()、CGContextEOFillPath()、CGContextFillPath()或CGContextStrokePath()函数来填充路径或绘制路径边框即可。

    提示:void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);可以替换后面几个方法,支持如下几种绘制方式:
    ①、kCGPathFill:指定填充路径。相当于CGContextFillPath()函数;
    ②、kCGPathEOFill:指定采用even-odd模式填充路径。相当于CGContextEOFillPath()函数;
    ③、kCGPathStroke:指定只绘制路径。相当于CGContextStrokePath();
    ④、kCGPathFillStroke:指定即绘制路径,也填充路径。如果采用这种绘制方式,必须使用该函数;
    ⑤、fCGPathEOFillStroke:指定只绘制路径,也采用even-odd模式填充路径。如果采用这种绘制方式,必须使用该函数。
    

例如:
这里写图片描述

//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    for (int i = 0; i < 10; i ++) {
        //开始定义路径
        CGContextBeginPath(ctx);
        //添加一段圆弧,最后一个参数1代表逆时针,0代表顺时针
        CGContextAddArc(ctx, i * 25, i * 25, (i + 1) * 8, M_PI * 1.5, M_PI, 0);
        //关闭路径
        CGContextClosePath(ctx);
        //设置填充颜色
        CGContextSetRGBFillColor(ctx, 1, 0, 1, (10 - i) * 0.1);
        //填充当前路径
        CGContextFillPath(ctx);
    }
}
提示:
①、void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise);该方法的第2、3个参数指定圆弧的圆心,第4个参数用于设置圆弧的半径,第5,6个参数用于设置圆弧的开始角度、结束角度,最后一个参数用于设置是否顺时针旋转。0代表顺时,1代表逆时。
②、void CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius);假设从当前点到P1(x1,y1)绘制一条线条,再从P1(x1,y1)到P2(x2,y2)绘制一条线条,然后该方法则绘制与上面两条线条相切且半径为radius的圆弧。

实例:绘制任意多角星

例如:
这里写图片描述

//该方法负责绘制圆角矩形
//x1,y1:是圆角矩形左上角的坐标;
//width、height:控制圆角矩形的宽、高;
//radius:控制圆角矩形的四个圆角的半径;
void CGContextAddRoundRect(CGContextRef c,CGFloat x1,CGFloat y1,CGFloat width,CGFloat height,CGFloat radius){
    //移动到左上角
    CGContextMoveToPoint(c, x1 + radius, y1);
    //添加一条连接到右上角的线段
    CGContextAddLineToPoint(c, x1 + width - radius, y1);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1, x1 + width, y1 + radius, radius);
    //添加一条连接到右下角的线段
    CGContextAddLineToPoint(c, x1 + width, y1 + height - radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1 + height, x1 + width - radius, y1 + height, radius);
    //添加一条连接到左下角的线段
    CGContextAddLineToPoint(c, x1 + radius, y1 + height);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1 + height, x1, y1 + height - radius, radius);
    //添加一条连接到左上角的线段
    CGContextAddLineToPoint(c, x1, y1 + radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1, x1 + radius, y1, radius);
}
//该方法负责绘制多角形。
//n:该参数通常应设为奇数,控制绘制N角星;
//dx、dy:控制N角星的中心;
//size:控制N角星的大小,中心距角的距离;
void CGContextAddStar(CGContextRef c, NSInteger n, CGFloat dx, CGFloat dy, NSInteger size){
    CGFloat dig = 4 * M_PI / n;
    //移动到指定点
    CGContextMoveToPoint(c , dx, dy + size);
    for (int i = 0 ; i <= n; i ++ ) {
        CGFloat x = sin(i * dig);
        CGFloat y = cos(i * dig);
        //绘制从当前连接到指定点的线条
        CGContextAddLineToPoint(c, x * size + dx, y * size + dy);
    }
}
//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //1、第一组
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加一个五角星路径
    CGContextAddStar(ctx, 5, 80, 150, 40);
    //添加一个圆角矩形路径
    CGContextAddRoundRect(ctx, 10, 30, 150, 70, 14);
    //关闭路径
    CGContextClosePath(ctx);
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
    //设置线宽
    CGContextSetLineWidth(ctx, 4);
    //绘制路径
    CGContextStrokePath(ctx);
    //2、第二组
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加一个五角星路径
    CGContextAddStar(ctx, 5, 240, 150, 40);
    //添加一个圆角矩形的路径
    CGContextAddRoundRect(ctx, 170, 30, 130, 70, 14);
    //关闭路径
    CGContextClosePath(ctx);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //采用填充并绘制路径的方式来绘制路径
    CGContextDrawPath(ctx, kCGPathFillStroke);
    //3、第三组
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加一个三角形路径
    CGContextAddStar(ctx, 3, 60, 220, 40);
    //关闭路径
    CGContextClosePath(ctx);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    //填充路径
    CGContextFillPath(ctx);
    //4、第四组
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加一个7角星的路径
    CGContextAddStar(ctx, 7, 160, 220, 40);
    //关闭路径
    CGContextClosePath(ctx);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
    //填充路径
    CGContextFillPath(ctx);
    //5、第五组
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加一个9角星的路径
    CGContextAddStar(ctx, 9, 260, 220, 40);
    //关闭路径
    CGContextClosePath(ctx);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);
    //填充路径
    CGContextFillPath(ctx);
}

实例:绘制圆环

这里写图片描述

@implementation CXCircleView
//该方法用于绘制扇形
//x,y:代表圆中心点
//radius1:代表小圆半径
//radius2:代表大圆半径
//angle:代表该扇形的角度,注意angle的大小不能为180度;
//注意:一般情况下中心点为大圆半径的一半
void CGContextAddRadius(CGContextRef c,CGFloat x, CGFloat y, CGFloat radius1, CGFloat radius2, CGFloat angle){
        CGFloat point_x1 = x + radius1 * cos(M_PI * (angle/360));
        CGFloat point_y1 = y - radius1 * sin(M_PI * (angle/360));
        //初始值小圆上角
        CGContextMoveToPoint(c, point_x1, point_y1);
        CGFloat point_x2 = x + radius2 * cos(M_PI * (angle/360));
        CGFloat point_y2 = y - radius2 * sin(M_PI * (angle/360));
        //移动到大圆上角
        CGContextAddLineToPoint(c, point_x2, point_y2);
        //移动到大圆下角
        CGContextAddArc(c, x, y,radius2, -M_PI * (angle/360), M_PI * (angle/360), 0);
        //移动到小圆下角
        CGContextAddLineToPoint(c, point_x1, point_y1 + 2 * radius1 * sin(M_PI * (angle/360)));
        //移动到初始值小圆上角
        CGContextAddArc(c, x, y,radius1, M_PI * (angle/360), -M_PI * (angle/360), 1);
}
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color andAngle:(CGFloat)angle andRadius1:(CGFloat)radius1 andRadius2:(CGFloat)radius2 andRadius3:(CGFloat)radius3{
    self = [super initWithFrame:frame];
    if (self) {
        self.radius1 = radius1;
        self.radius2 = radius2;
        self.radius3 = radius3;
        self.color = color;
        self.angle = angle;
    }
    self.backgroundColor = [UIColor clearColor];
    return self;
}
//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加扇形
    CGContextAddRadius(ctx, self.radius3, self.radius3, self.radius1, self.radius2 ,self.angle);
    //关闭路径
    CGContextClosePath(ctx);
    //设置填充颜色
    CGContextSetFillColorWithColor(ctx, self.color.CGColor);
    //填充
    CGContextFillPath(ctx);
}

7、绘制曲线

Quartz 2D提供了CGContextAddCurveToPoint()和CGContextAddQuadCurveToPoint()两个函数想CGContextRef的当前路径上添加曲线,前者用于添加贝塞尔曲线,后者用于添加二次曲线。

  • ①、 确定一条贝塞尔曲线需要4个点:开始点、第一个控制点、第二个控制点、结束点。
    void CGContextAddCurveToPoint(CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y);方法则负责绘制从路径的当前点(作为开始点)到结束点(x、y)的贝塞尔曲线,其中。cpx1、cpy1定义第一个控制点的坐标;cpx2、cpy2定义第二个控制点的坐标。
  • ②、确定一条二次曲线需要三个点:开始点、控制点、结束点。
    void CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y);方法则负责从路径的当前点(作为起始点)到结束点(x、y)的二次曲线,其中cpx、cpy定义控制点的坐标;

实例:使用曲线绘制多瓣花朵

这里写图片描述

//该方法负责绘制花朵
//n:该参数控制花朵的花瓣数;
//dx、dy:控制花朵的位置;
//size:控制花朵的大小;
void CGContextAddFlower(CGContextRef c,NSInteger n, CGFloat dx,CGFloat dy,CGFloat size,CGFloat length){
    //移动到指定点
    CGContextMoveToPoint(c, dx, dy + size);
    CGFloat dig = 2 * M_PI / n;
    //采用循环添加n段二次曲线路径
    for (int i = 0; i < n + 1; i ++) {
        //计算控制点坐标
        CGFloat ctr1X = sin((i - 0.5) * dig) * length + dx;
        CGFloat ctr1Y = cos((i - 0.5) * dig) * length + dy;
        //计算结束点的坐标
        CGFloat x = sin(i * dig) * size + dx;
        CGFloat y = cos(i * dig) * size + dy;
        //添加二次曲线路径
        CGContextAddQuadCurveToPoint(c, ctr1X, ctr1Y, x, y);
    }
}
//重写该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //开始添加路径
    CGContextBeginPath(ctx);
    //添加5瓣花朵的路径
    CGContextAddFlower(ctx, 5, 50, 100, 30, 80);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    //填充路径
    CGContextFillPath(ctx);
    //添加6瓣花朵的路径
    CGContextAddFlower(ctx, 6, 160, 100, 30, 80);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);
    //填充路径
    CGContextFillPath(ctx);
    //添加7瓣花朵的路径
    CGContextAddFlower(ctx, 7, 270, 100, 30, 80);
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //填充路径
    CGContextFillPath(ctx);
    //关闭路径
    CGContextClosePath(ctx);
}

8、在内存中绘图

提示:前面介绍的都是通过扩展UIView、重写drawRect:方法进行绘图,这种绘图方法是直接在UIView控件上绘制所有的图形——由于每次该控件显示出来时,drawRect:方法都会被调用,这意味着每次该控件显示出来时,程序都需要重绘所有的图形,很明显,这种方式的性能并不好。除此之外,总有些时候需要在内存中绘制图形,这样即可导出到手机本地,也可上传到网络上。

Quartz 2D提供了一个非常便捷的函数:void UIGraphicsBeginImageContext(CGSize size);该函数用于准备绘图环境。当图形绘制完成后,调用void UIGraphicsEndImageContext(void);函数结束绘图和关闭绘图环境;
在内存中绘图的步骤如下:Graphics.h

  1. 调用void UIGraphicsBeginImageContext(CGSize size);函数准备绘图环境;
  2. 调用CGContextRef UIGraphicsGetCurrentContext(void);函数获取绘图CGContextRef;
  3. 用前面介绍的绘制集合图像、使用路径等方式进行绘图;
  4. 调用UIImage* UIGraphicsGetImageFromCurrentImageContext(void);函数获取当前绘制的图形,该方法返回一个UIImage对象;
  5. 调用void UIGraphicsEndImageContext(void);函数结束绘图,并关闭绘图环境。

另外还可以调用如下的方法进行绘图:Graphics.h

  • (1)、void UIRectFill(CGRect rect);向当前绘图环境所创建的内存中的图片填充一个矩形;
  • (2)、void UIRectFillUsingBlendMode(CGRect rect, CGBlendMode blendMode);向当前绘图环境所创建的内存中的图片上填充一个矩形,绘制使用指定的混合模式;
  • (3)、void UIRectFrame(CGRect rect);向当前绘图环境所创建的内存中的图片上绘制一个矩形边框;
  • (4)、void UIRectFrameUsingBlendMode(CGRect rect, CGBlendMode blendMode);向当前绘图环境所创建的内存中的图片上绘制一个矩形边框,绘制使用指定的混合模式。

例如:
这里写图片描述

- (UIImage *)drawImage:(CGSize)size{
    //创建内存中的图片
    UIGraphicsBeginImageContext(size);
    //获取绘图CGContextRef
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //设置线宽
    CGContextSetLineWidth(ctx, 8);
    //------下面开始向内存中绘制图形------
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);
    //绘制一个矩形边框
    CGContextStrokeRect(ctx, CGRectMake(30, 30, 120, 60));
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);
    //填充一个矩形
    CGContextFillRect(ctx, CGRectMake(180, 30, 120, 60));
    //设置线条颜色
    CGContextSetRGBStrokeColor(ctx, 0, 1, 1, 1);
    //绘制一个椭圆
    CGContextStrokeEllipseInRect(ctx, CGRectMake(30, 120, 120, 60));
    //设置填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);
    //填充一个椭圆
    CGContextFillEllipseInRect(ctx, CGRectMake(180, 120, 120, 60));
    //获取该绘图Context中的图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    //------结束绘图------
    UIGraphicsEndImageContext();
    //获取当前应用路径中Documents目录下的制定文件名对应的文件路径
    NSString* path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]stringByAppendingPathComponent:@"newPng.png"];
    //保存PNG图片
    [UIImagePNGRepresentation(newImage) writeToFile:path atomically:YES];
    return newImage;
}

实例:绘图板

为了保证用户每次绘图的内容不会丢失,将会在内存中创建一张图片,点那个用户开始绘图时,程序会通过重写drawRect:方法进行实时绘制,当用户想要绘制的图形确定下来后,将该图形绘制到内存中的图片中。

ViewController.m的实现部分

#import "ViewController.h"
#import "MyView.h"
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface ViewController ()
{
    NSArray* colors;
    MyView* view;
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    view = [[MyView alloc]initWithFrame:self.view.frame];
    self.view = view;
    self.view.backgroundColor = [UIColor whiteColor];
    colors = [NSArray arrayWithObjects:[UIColor redColor],[UIColor greenColor],[UIColor blueColor],[UIColor yellowColor],[UIColor purpleColor],[UIColor cyanColor],[UIColor blackColor], nil];
    //颜色
    UISegmentedControl* segmentedControl1 = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:@"红",@"绿",@"蓝",@"黄",@"紫",@"青",@"黑", nil]];
    segmentedControl1.frame = CGRectMake(20, 20, SCREEN_WIDTH - 40, 40);
    segmentedControl1.selectedSegmentIndex = 0;
    [segmentedControl1 addTarget:self action:@selector(changeColor:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:segmentedControl1];
    //形状
    UISegmentedControl* segmentedControl2 = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:@"直线",@"矩形",@"椭圆",@"圆角矩形",@"自由",@"清空", nil]];
    segmentedControl2.frame = CGRectMake(10, SCREEN_HEIGHT - 40, SCREEN_WIDTH - 20, 40);
    segmentedControl2.selectedSegmentIndex = 0;
    [segmentedControl2 addTarget:self action:@selector(changeShape:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:segmentedControl2];
}
- (void)changeColor:(UISegmentedControl *)sender{
    view.currentColor = [colors objectAtIndex:sender.selectedSegmentIndex];
}
- (void)changeShape:(UISegmentedControl *)sender{
    if (sender.selectedSegmentIndex == 5) {
        view.shape = (ShareType)sender.selectedSegmentIndex;
        [view clearImage];
    }else{
        view.shape = (ShareType)sender.selectedSegmentIndex;
    }
}

MyView.h的接口部分

#import <UIKit/UIKit.h>
typedef enum{
    kLineShape = 0,
    kRectShape,
    kEllipseShape,
    kRoundRectShape,
    kPenShape,
    kClearShape
}ShareType;
@interface MyView : UIView
@property (nonatomic ,assign)UIColor* currentColor;
@property (nonatomic ,assign)ShareType shape;
- (void)clearImage;
@end

MyView.m的实现部分

#import "MyView.h"
@implementation MyView
{
    CGContextRef buffCtx;
    UIImage* image;
    CGPoint firstTouch,prevTouch,lastTouch;
}
//圆角矩形
void CGContextAddRoundRect(CGContextRef c,CGFloat x1,CGFloat y1,CGFloat width,CGFloat height,CGFloat radius){
    //移动到左上角
    CGContextMoveToPoint(c, x1 + radius, y1);
    //添加一条连接到右上角的线段
    CGContextAddLineToPoint(c, x1 + width - radius, y1);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1, x1 + width, y1 + radius, radius);
    //添加一条连接到右下角的线段
    CGContextAddLineToPoint(c, x1 + width, y1 + height - radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1 + height, x1 + width - radius, y1 + height, radius);
    //添加一条连接到左下角的线段
    CGContextAddLineToPoint(c, x1 + radius, y1 + height);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1 + height, x1, y1 + height - radius, radius);
    //添加一条连接到左上角的线段
    CGContextAddLineToPoint(c, x1, y1 + radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1, x1 + radius, y1, radius);
}
- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        //初始化时当前颜色默认设为红色
        self.currentColor = [UIColor redColor];
        //初始化时当前图形类型默认为直线
        self.shape = kLineShape;
        //创建内存中的图片
        UIGraphicsBeginImageContext(self.bounds.size);
        //获取向内存中的图片执行绘图的CGContextRef
        buffCtx = UIGraphicsGetCurrentContext();
    }
    return self;
}
//当用户手指开始碰触时激发该方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    if (self.shape != kClearShape) {
        UITouch* touch = [touches anyObject];
        //获取触碰点坐标
        firstTouch = [touch locationInView:self];
        //如果当前正在进行自由绘制,prevTouch代表第一个碰触点
        if (self.shape == kPenShape) {
            prevTouch = firstTouch;
        }
    }
}
//当用户手指在空间上拖动时不段激发该方法
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    if (self.shape != kClearShape) {
        UITouch* touch = [touches anyObject];
        //获取碰触点坐标
        lastTouch = [touch locationInView:self];
        //如果当前正在进行自由绘制
        if (self.shape == kPenShape) {
            //向内存中的图片执行绘制
            [self draw:buffCtx];
            //取出内存中的图片,保存到image中
            image = UIGraphicsGetImageFromCurrentImageContext();
        }
        //通知该控件重绘,此时会实时绘制起始点与用户手指拖动点之间的形状
        [self setNeedsDisplay];
    }
}
//当用户手指离开控件时激发该方法
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    if (self.shape != kClearShape) {
        UITouch* touch = [touches anyObject];
        //获取离开触碰点的坐标
        lastTouch = [touch locationInView:self];
        //向内存中的图片进行绘制,即把最终的图形绘制到内存中的图片上
        [self draw:buffCtx];
        image = UIGraphicsGetImageFromCurrentImageContext();
        //重绘通知
        [self setNeedsDisplay];
    }
}
- (void)drawRect:(CGRect)rect{
    if (self.shape == kClearShape) {
    }else{
        //获取绘图上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        //将内存中的图片绘制出来
        [image drawAtPoint:CGPointZero];
        [self draw:ctx];
    }
}
- (CGRect)curRect{
    return CGRectMake(firstTouch.x, firstTouch.y, lastTouch.x - firstTouch.x, lastTouch.y - firstTouch.y);
}
- (void)draw:(CGContextRef)ctx{
    //设置线条颜色
    CGContextSetStrokeColorWithColor(ctx, self.currentColor.CGColor);
    //设置填充颜色
    CGContextSetFillColorWithColor(ctx, self.currentColor.CGColor);
    //设置宽度
    CGContextSetLineWidth(ctx, 2);
    //设置该CGContextRef是否应该抗锯齿(即光滑圆形曲线边缘)
    CGContextSetShouldAntialias(ctx, YES);
    CGFloat leftTopX,leftTopY;
    switch (self.shape) {
        case kLineShape:
            //添加从firstTouch到lastTouch的路径
            CGContextMoveToPoint(ctx, firstTouch.x, firstTouch.y);
            CGContextAddLineToPoint(ctx, lastTouch.x, lastTouch.y);
            //绘制路径
            CGContextStrokePath(ctx);
            break;
        case kRectShape:
            CGContextFillRect(ctx, [self curRect]);
            break;
        case kEllipseShape:
            CGContextFillEllipseInRect(ctx, [self curRect]);
            break;
        case kRoundRectShape:
            //计算左上角坐标
            leftTopX = firstTouch.x < lastTouch.x?firstTouch.x:lastTouch.x;
            leftTopY = firstTouch.y < lastTouch.y?firstTouch.y:lastTouch.y;
            //添加圆角矩形的路径
            CGContextAddRoundRect(ctx, leftTopX, leftTopY, fabs(lastTouch.x - firstTouch.x), fabs(lastTouch.y - firstTouch.y), 16);
            //填充路径
            CGContextFillPath(ctx);
            break;
        case kPenShape:
            //添加从prevTouch到lastTouch的路径
            CGContextMoveToPoint(ctx, prevTouch.x, prevTouch.y);
            CGContextAddLineToPoint(ctx, lastTouch.x, lastTouch.y);
            //绘制路径
            CGContextStrokePath(ctx);
            //使用prevTouch保存当前点
            prevTouch = lastTouch;
            break;
        default:
            break;
    }
}
- (void)clearImage{
    image = nil;
    [self setNeedsDisplay];
    UIGraphicsEndImageContext();
    UIGraphicsBeginImageContext(self.bounds.size);
    buffCtx = UIGraphicsGetCurrentContext();
}
@end

9、绘制位图

为了绘制位图,UIImage本身已经提供了如下方法:UIImage.h

  • (1)、- (void)drawAtPoint:(CGPoint)point;将该图片本身绘制到当前绘图CGContextRef的指定点。调用该方法必须传入一个CGPoint参数,指定该图片的绘制点;
  • (2)、- (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;属于前一个方法的增强版,它可以指定绘制图片的叠加模式和透明度;
  • (3)、- (void)drawInRect:(CGRect)rect;将该图片本身绘制到当前绘图CGContextRef的指定区域内。调用该方法必须传入一个CGRect参数,指定该图片的绘制区域;
  • (4)、- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;属于前一个方法的增强版,他可以指定绘制图片的叠加模式和透明度;

如果希望对图片指定旋转等变换,则可借助如下函数来绘制图片:CGContext.h

  • (1)、void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image);该函数用于将image绘制到rect区域内;
  • (2)、void CGContextDrawTiledImage(CGContextRef c, CGRect rect, CGImageRef image);该函数采用“平铺”模式将image绘制到rect区域内;
    如果需要“挖取”已有图片的部分或者全部,可借助如下函数:CGImage.h
  • (1)、CGImageRef CGImageCreateCopy(CGImageRef image);该函数将会创建image图片的副本;
    = (2)、CGImageRef CGImageCreateWithImageInRect(CGImageRef image, CGRect rect);该函数将“挖取”image图片中的rect区域;

实例:扩展UIImage的功能

#import <QuartzCore/QuartzCore.h>
extern CGImageRef UIGetScreenImage();
@implementation UIImage (XYUIImageCategory)
//对指定的UI控件进行截屏
+ (UIImage *)captureScreen{
    //需要先声明该外部函数
    //调用UIGetScreenImage()函数执行截屏
    CGImageRef screen = UIGetScreenImage();
    //获取截屏到的图片
    UIImage* newImage = [UIImage imageWithCGImage:screen];
    return newImage;
}
+ (UIImage *)captureView:(UIView *)targetView{
    //获取目标UIView所在的区域
    CGRect rect = targetView.frame;
    //开始绘图
    UIGraphicsBeginImageContext(rect.size);
    //获取当前的绘图Context
    CGContextRef context = UIGraphicsGetCurrentContext();
    //调用CALayer的方法将当前控件绘制到绘图Context中
    [targetView.layer renderInContext:context];
    //获取该绘图Context中的图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
//定义一个方法用于“挖取”图片的指定区域
- (UIImage *)imageAtRect:(CGRect)rect{
    //获取该UIImage图片对应的CGImageRef对象
    CGImageRef srcImage = [self CGImage];
    //从srcImage中“挖取”rect区域
    CGImageRef imageRef = CGImageCreateWithImageInRect(srcImage, rect);
    //将“挖取”出来的CGImageRef转化为UIImage对象
    UIImage* subImage = [UIImage imageWithCGImage:imageRef];
    return subImage;
}
//保持图片纵横比缩放,最短边必须匹配targetSize指定的大小
//可能有一条边的额长度会超过targetSize指定的大小
- (UIImage *)imageByScalingAspectToMinSize:(CGSize)targetSize{
    //获取原图片的宽和高
    CGSize imageSize = self.size;
    CGFloat width = imageSize.width;
    CGFloat height = imageSize.height;
    //获取图片缩放目标大小的宽和高
    CGFloat targetWidth = targetSize.width;
    CGFloat targetHtight = targetSize.height;
    //定义图片缩放后的宽度
    CGFloat scaledWidth = targetWidth;
    //定义图片缩放后的高度
    CGFloat scaledHeight = targetHtight;
    CGPoint anchorPoint = CGPointZero;
    //如果原图片的大小与缩放的目标大小不相等
    if (!CGSizeEqualToSize(imageSize, targetSize)) {
        //计算水平方向上的缩放因子
        CGFloat xFactor = targetWidth/width;
        //计算垂直方向上的缩放因子
        CGFloat yFactor = targetHtight/height;
        //定义缩放因子scaleFactor为两个缩放因子中较大的一个
        CGFloat scaleFactor = xFactor > yFactor? xFactor:yFactor;
        //根据缩放因子计算图片缩放后的宽度和高度
        scaledWidth = width * scaleFactor;
        scaledHeight = height * scaleFactor;
        if (xFactor > yFactor) {
            //如果横向的缩放因子大于纵向的缩放因子,图片在上面和下面有空白
            //那么图片在纵向上需要下移一段距离,保持图片在中间
            anchorPoint.y = (targetHtight - scaledHeight) * 0.5;
        }else if (xFactor < yFactor){
            //如果横向的缩放因子小于纵向的缩放因子,图片在左边和有边有空白
            //那么图片在横向上需要右移一段距离,保持图片在中间
            anchorPoint.x = (targetWidth - scaledWidth) * 0.5;
        }
    }
    //开始绘图
    UIGraphicsBeginImageContext(targetSize);
    //定义图片缩放后的区域
    CGRect anchorRect = CGRectZero;
    anchorRect.origin = anchorPoint;
    anchorRect.size.width = scaledWidth;
    anchorRect.size.height = scaledHeight;
    //将图片本身绘制到auchorRect区域中
    [self drawInRect:anchorRect];
    //获取绘制后生成的新图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
//保持图片纵横比缩放,最长边匹配targetSize的大小即可
//可能有一条边的长度会大于targetSize指定的大小
- (UIImage *)imageBySxakingAspectToMaxSize:(CGSize)targetSize{
    //获取原图片的宽和高
    CGSize imageSize = self.size;
    CGFloat width = imageSize.width;
    CGFloat height = imageSize.height;
    //获取图片缩放目标大小的宽和高
    CGFloat targetWidth = targetSize.width;
    CGFloat targetHtight = targetSize.height;
    //定义图片缩放后的宽度
    CGFloat scaledWidth = targetWidth;
    //定义图片缩放后的高度
    CGFloat scaledHeight = targetHtight;
    CGPoint anchorPoint = CGPointZero;
    //如果原图片的大小与缩放的目标大小不相等
    if (!CGSizeEqualToSize(imageSize, targetSize)) {
        //计算水平方向上的缩放因子
        CGFloat xFactor = targetWidth/width;
        //计算垂直方向上的缩放因子
        CGFloat yFactor = targetHtight/height;
        //定义缩放因子scaleFactor为两个缩放因子中较大的一个
        CGFloat scaleFactor = xFactor < yFactor? xFactor:yFactor;
        //根据缩放因子计算图片缩放后的宽度和高度
        scaledWidth = width * scaleFactor;
        scaledHeight = height * scaleFactor;
        //如果横向上的缩放因子大于纵向上的缩放因子,那么图片在纵向上需要裁切
        //如果横向上的缩放因子小于纵向上的缩放因子,那么图片在横向上需要裁切
        if (xFactor < yFactor) {
            anchorPoint.y = (targetHtight - scaledHeight) * 0.5;
        }else if (xFactor > yFactor){
            anchorPoint.x = (targetWidth - scaledWidth) * 0.5;
        }
    }
    //开始绘图
    UIGraphicsBeginImageContext(targetSize);
    //定义图片缩放后的区域
    CGRect anchorRect = CGRectZero;
    anchorRect.origin = anchorPoint;
    anchorRect.size.width = scaledWidth;
    anchorRect.size.height = scaledHeight;
    //将图片本身绘制到auchorRect区域中
    [self drawInRect:anchorRect];
    //获取绘制后生成的新图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
//不保持图片纵横比缩放
- (UIImage *)imageByScalingToSize:(CGSize)targetSize{
    //开始绘图
    UIGraphicsBeginImageContext(targetSize);
    //定义图片缩放后的区域,无须保持纵横比,所以直接缩放
    CGRect anchorRect = CGRectZero;
    anchorRect.origin = CGPointZero;
    anchorRect.size = targetSize;
    //将图片本身绘制到auchorRect区域中
    [self drawInRect:anchorRect];
    //获取绘制后生成的新图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
//对图片按弧度执行旋转
- (UIImage *)imageRotateByRadians:(CGFloat)radians{
    //定义一个执行旋转的CGAffineTransform结构体
    CGAffineTransform t = CGAffineTransformMakeRotation(radians);
    //对图片的原始区域执行旋转,获取旋转后的区域
    CGRect rotateRect = CGRectApplyAffineTransform(CGRectMake(0.0, 0.0, self.size.width, self.size.height), t);
    //获取图片旋转后的大小
    CGSize rotateSize = rotateRect.size;
    //获取绘制位图的上下文
    UIGraphicsBeginImageContext(rotateSize);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //指定坐标变换,将坐标中心平移到图片的中心
    CGContextTranslateCTM(ctx, rotateSize.width/2, rotateSize.height/2);
    //指定坐标变换,旋转过radians弧度
    CGContextRotateCTM(ctx, radians);
    //执行坐标变换,执行缩放
    CGContextScaleCTM(ctx, 1.0, -1.0);
    //绘制图片
    CGContextDrawImage(ctx, CGRectMake(-self.size.width/2, -self.size.height/2, self.size.width, self.size.height), self.CGImage);
    //获取绘制后生成的新图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
//对图片按角度执行旋转
- (UIImage *)imageRotateByDegress:(CGFloat)degress{
    return [self imageRotateByRadians:degress * M_PI/180];
}
//保存到本地
- (void)saveToDocuments:(NSString *)fileName{
    //获取当前应用路径中Documents目录下的指定文件名对应的文件路径
    NSString* path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]stringByAppendingPathComponent:fileName];
    //保存PNG图片
    [UIImagePNGRepresentation(self) writeToFile:path atomically:YES];
    //1.0代表图片压缩比率 保存JPEG图片
//    [UIImageJPEGRepresentation(self, 1.0) writeToFile:path atomically:YES];
}

注意:extern CGImageRef UIGetScreenImage();函数并非公开,在Xcode5以下时可以需要先用extern声明该函数,然后使用。但是在Xcode6以上版本中不允许使用。

三、图形变换

1、使用坐标变换

  • (1)、void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty);平移坐标系统。该方法相当于把原来位于(0,0)位置的坐标原点平移到(tx,ty)点。在平移后的坐标系统上绘制图形时,所有坐标点的X坐标都相当于增加了tx,所有点的Y坐标都相当于增加了ty;
  • (2)、void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy);缩放坐标系统。该方法控制坐标系统水平方向上缩放sx,垂直方向上缩放sy。在缩放后的坐标系统上绘制图形时,所有点的X坐标都相当于乘以sx因子,所有点的Y坐标都相当于乘以sy因子;
  • (3)、void CGContextRotateCTM(CGContextRef c, CGFloat angle); CGContextRotateCTM(CGContextRef c, CGFloat angle);旋转坐标系统。该方法控制坐标旋转了angle弧度。在缩放后的坐标系统上绘制图形时,所有坐标点的X、Y坐标都相当于旋转了angle弧度之后的坐标;

为了让开发者在进行坐标变换时无需计算多次坐标变换后的累加结果,Quartz 2D还提供了如下两个方法来保存、恢复绘图状态;

  • (1)、void CGContextSaveGState(CGContextRef c);保存当前的绘图状态;
  • (2)、void CGContextRestoreGState(CGContextRef c);恢复之前保存的绘图状态;

    说明:CGContextSaveGState()函数保存的绘图状态,不仅包括当前坐标系统的状态,也包括当前设置的填充风格、线条风格、阴影风格等各种绘图状态。当CGContextSaveGState()函数不会保存当前绘制的图形。
    

    这里写图片描述

- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //设置使用半透明的填充颜色
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 0.3);
    //坐标系统平移到-40、200点
    CGContextTranslateCTM(ctx, -40, 200);
    for (int i = 0; i < 50; i ++) {
        CGContextTranslateCTM(ctx, 50, 50);
        CGContextScaleCTM(ctx, 0.93, 0.93);
        CGContextRotateCTM(ctx, -M_PI/10);
        CGContextFillRect(ctx, CGRectMake(0, 0, 150, 75));
    }
}

2、坐标变换与路径结合使用

如果在坐标变换之后的坐标系统内创建路径,那么创建路径所用的每个点的坐标也是变换后的结果。因此,整个路径都是基于“坐标变换”后的坐标系统;

实例:雪花飘飘

static CGPoint snowPos[]= {
    {20,4},
    {50,4},
    {80,4},
    {110,4},
    {140,4},
    {170,4},
    {200,4},
    {230,4},
    {260,4},
    {290,4}
};
//计算雪花的数量
static NSInteger snowCount = sizeof(snowPos)/sizeof(snowPos[0]);
@implementation MyView
void CGContextAddFlower(CGContextRef c,NSInteger n, CGFloat dx,CGFloat dy,CGFloat size,CGFloat length){
    //移动到指定点
    CGContextMoveToPoint(c, dx, dy + size);
    CGFloat dig = 2 * M_PI / n;
    //采用循环添加n段二次曲线路径
    for (int i = 0; i < n + 1; i ++) {
        //计算控制点坐标
        CGFloat ctr1X = sin((i - 0.5) * dig) * length + dx;
        CGFloat ctr1Y = cos((i - 0.5) * dig) * length + dy;
        //计算结束点的坐标
        CGFloat x = sin(i * dig) * size + dx;
        CGFloat y = cos(i * dig) * size + dy;
        //添加二次曲线路径
        CGContextAddQuadCurveToPoint(c, ctr1X, ctr1Y, x, y);
    }
}
- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        //控制每个0.2秒执行一次setNeedDisplay方法刷新自己
        [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
    }
    return self;
}
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //设置采用白色作为填充色
    CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
    for (int i = 0; i < snowCount; i ++) {
        //保存当前绘图状态
        CGContextSaveGState(ctx);
        //平移坐标系统
        CGContextTranslateCTM(ctx, snowPos[i].x, snowPos[i].y);
        //旋转坐标系统
        CGContextRotateCTM(ctx, (arc4random() % 6 - 3)*M_PI/10);
        //控制雪花下落
        snowPos[i].y += arc4random() % 8;
        if (snowPos[i].y > 320) {
            snowPos[i].y = 4;
        }
        //创建并绘制“雪花”
        CGContextAddFlower(ctx, 6, 0, 0, 4, 8);
        CGContextFillPath(ctx);
        //恢复绘制状态
        CGContextRestoreGState(ctx);
    }
}

3、使用矩形变换

  • (1)、void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform);使用transform变换矩形对CGContextRef的坐标系统执行变换,通过使用坐标矩形可以对坐标系统执行任意变换。
  • (2)、CGAffineTransform CGContextGetCTM(CGContextRef c);获取CGContextRef的坐标系统的变换矩阵;

上面两个方法中,CGContextConcatCTM()函数应用变换矩阵对坐标系统执行变换,而CGContextGetCTM()方法则用于获取坐标的变换矩阵。这两个方法都涉及一个API:CGAffineTransform——它代表变换矩阵。

为了创建CGAffineTransform,可以调用如下函数进行创建:

  • (1)、CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty);创建进行位移变换的变换矩阵。该方法相当于把原来位于(0,0)位置的坐标原点平移到(tx,ty)点。在平移后的坐标系统上绘制图形时,所有坐标点的X坐标都相当于增加了tx,所有点的Y坐标都相当于增加了ty;
  • (2)、CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);创建进行缩放变换的变换矩阵。该方法控制坐标系统水平方向上缩放sx,垂直方向上缩放sy。在缩放后的坐标系统上绘制图形时,所有点的X坐标都相当于乘以sx因子,所有点的Y坐标都相当于乘以sy因子;
  • (3)、CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle);创建进行旋转变换的变换矩阵。该方法控制坐标旋转了angle弧度。在缩放后的坐标系统上绘制图形时,所有坐标点的X、Y坐标都相当于旋转了angle弧度之后的坐标;
  • (4)、CGAffineTransform CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty);该函数使用自定义变换矩阵执行变换;
  • (5)、CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);对已有的变换矩阵t额外增加位移变换;
  • (6)、CGAffineTransform CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);对已有的变换矩阵t额外增加缩放变换;
  • (7)、CGAffineTransform CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);对已有的变换矩阵t额外增加旋转变换;
  • (8)、CGAffineTransform CGAffineTransformInvert(CGAffineTransform t);对已有的变换矩阵t进行反转;
  • (9)、CGAffineTransform CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);将两个变换矩阵进行叠加;

通过上面的代码得到CGAffineTransform变换矩阵之后,即可对指定的绘图CGContextRef指定相应地坐标变换,也可以对指定的点、矩形等执行坐标变换。可通过如下方法对点、矩形等几何形状执行变换:

  • (1)、CGPoint CGPointApplyAffineTransform(CGPoint point, CGAffineTransform t);对指定的CGPoint执行变换,函数返回坐标变换后的CGPoint;
  • (2)、CGSize CGSizeApplyAffineTransform(CGSize size, CGAffineTransform t);对指定的CGSize执行变换,函数返回坐标变换后的CGSize;
  • (3)、CGRect CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t);对指定的CGRect执行变换,函数返回坐标变换后的CGSize;
    这里写图片描述
void CGContextSkewCTM(CGContextRef ctx,CGFloat angle){
    CGAffineTransform transform = CGAffineTransformMake(1, 0, -tanf(angle), 1, 0, 0);
    CGContextConcatCTM(ctx, transform);
}
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 0.3);
    //坐标系统平移到-40,200点
    CGContextTranslateCTM(ctx, 120, 5);
    for (int i = 0; i < 20; i ++) {
        //平移坐标系统
        CGContextTranslateCTM(ctx, 50, 30);
        //缩放坐标系统
        CGContextScaleCTM(ctx, 0.9, 0.9);
        //倾斜变换
        CGContextSkewCTM(ctx, M_PI/10);
        //填充矩形
        CGContextFillRect(ctx, CGRectMake(0, 0, 120, 60));
    }
}

四、控制叠加模式

待续;

五、处理填充

前面介绍的都是使用一种颜色来填充区域,除此之外,Quartz 2D还允许使用颜色渐变、位图来填充指定区域。

  • (1)、void CGContextDrawLinearGradient(CGContextRef context,
    CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint,
    CGGradientDrawingOptions options);设置线性渐变填充,其中gradient参数代表渐变对象,startPoint参数设置线性渐变的开始点,endPoint参数设置线性渐变的结束点,options可支持kCGGradientDrawsBeforeStartLocation(扩展填充起始点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域);
  • (2)、void CGContextDrawRadialGradient(CGContextRef context, CGGradientRef gradient, CGPoint startCenter, CGFloat startRadius,
    CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options);设置圆形渐变填充,其中,gradient参数代表渐变对象,startCenter参数设置起始圆的圆心,startRadius设置起始圆的半径,endCenter参数代表结束圆的圆心,endRadius代表结束圆的半径,options可支持kCGGradientDrawsBeforeStartLocation(扩展填充起始点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域);

上面两个函数中都需要一个CGGradientRef参数,该参数代表渐变颜色,为了获取CGGradientRef,可以调用如下函数:

CGGradientRef CGGradientCreateWithColorComponents(CGColorSpaceRef space, const CGFloat components[], const CGFloat locations[], size_t count);
其中的space参数用于指定该渐变所使用颜色空间(如RGB、CMYK、Gray等颜色空间),components用于根据不同的颜色空间设置多种颜色,locations参数指定各颜色点的分布位置(如果该参数指定为NULL,各颜色点将会均匀分布),count参数指定该渐变包含多少中颜色。
@implementation FKBlendView{
    CGGradientRef gradient;
}
- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.opaque = YES;
        self.backgroundColor = [UIColor blackColor];
        //设置每次清空上一次绘制的内容
        self.clearsContextBeforeDrawing = YES;
        //创建RGB颜色空间
        CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
        //定义渐变颜色:红、绿、蓝(可以根据需要增加或减少渐变颜色)
        CGFloat colors[] = {
            1,0,0,1,
            0,1,0,1,
            0,0,1,1,
        };
        //创建渐变对象
        gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/sizeof(colors[0]) * 2);
        CGColorSpaceRelease(rgb);
    }
    return self;
}
//当重新设置渐变类型时,通知该控件重绘自己
- (void)setType:(GradientType)newType{
    if (_type != newType) {
        _type = newType;
        [self setNeedsDisplay];
    }
}
//当重新设置是否扩展填充开始点之前的区域时,通知该控件重绘自己
- (void)setBeforeStart:(BOOL)yn{
    if (_beforeStart != yn) {
        _beforeStart = yn;
        [self setNeedsDisplay];
    }
}
//当重新设置是否扩展结束点之后的区域时,通知该控件重绘自己
- (void)setAfterEnd:(BOOL)yn{
    if (_afterEnd != yn) {
        _afterEnd = yn;
        [self setNeedsDisplay];
    }
}
//根据用户选择计算渐变选项
- (CGGradientDrawingOptions)drawingOptions{
    CGGradientDrawingOptions option = 0;
    //如果beforeStart为YES
    if (self.beforeStart) {
        //使用kCGGradientDrawsBeforeStartLocation选项
        option |= kCGGradientDrawsBeforeStartLocation;
    }
    //如果afterEnd为YES
    if (self.afterEnd) {
        //使用kCGGradientDrawsAfterEndLocation选项
        option |= kCGGradientDrawsAfterEndLocation;
    }
    return option;
}
//重新该方法绘制该控件
- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //获取该组件四周各减少20px的区域
    CGRect clip = CGRectInset(CGContextGetClipBoundingBox(ctx), 20.0, 20.0);
    //定义开始绘制的开始点和结束点
    CGPoint start,end;
    CGFloat startRadius,endRadius;
    //创建一个Clip区域,用于控制只在clip区域内绘制
    CGContextClipToRect(ctx, clip);
    switch (self.type) {
        case kLinearGradient://如果用户选择了线性渐变
            //以左上角(但Y坐标为clip内1/4处)作为线性渐变的起始点
            start = CGPointMake(clip.origin.x, clip.origin.y + clip.size.height * 0.25);
            //以左上角(但Y坐标为clip内3/4处)作为线性渐变的结束始点
            end = CGPointMake(clip.origin.x, clip.origin.y + clip.size.height * 0.75);
            //填充线性渐变
            CGContextDrawLinearGradient(ctx, gradient, start, end, [self drawingOptions]);
            break;
        case kRadialGradient://如果用户选择了圆形渐变
            //定义圆形渐变的开始圆心、结束圆心都是clip的中心
            start = end = CGPointMake(CGRectGetMidX(clip), CGRectGetMidY(clip));
            //计算clip区域的长、宽中较短的一条
            CGFloat r = clip.size.width < clip.size.height?clip.size.width:clip.size.height;
            //计算r的1/8作为起始圆的半径
            startRadius = r * 0.125;
            //计算r的1/2作为起始圆的半径
            endRadius = r ;
            //填充圆形渐变
            CGContextDrawRadialGradient(ctx, gradient, start, startRadius, end, endRadius, [self drawingOptions]);
            break;
        default:
            break;
    }
    //绘制矩形边框
    CGContextSetRGBStrokeColor(ctx, 1, 1, 1, 1);
    CGContextStrokeRectWithWidth(ctx, clip, 2.0);
}

2、模式填充

待续

六、使用Core Image滤镜

  使Core Image滤镜是iOS5的新增框架,通过使用这个框架,我们可以非常容易地对地图片进行各种特效处理,包括进行色彩调节、降噪、扭曲。

Core Image的三个核心API如下:

  • (1)、CIContext:它是Core Image处理的核心API,所有图片的处理都在它的管理下完成;
  • (2)、CIFilter:它代表过滤器。所有的过滤器都由该CIFilter代表,在创建CIFilter时需要传入不同的参数即可创建不同类型的过滤器;
  • (3)、CIImage:它代表Core Image过滤器处理的图片,CIFilter过滤器的输入图片、输出图片都由该CIImage代表。程序可通过UIImage、图片文件或像素数据来创建CIImage。

了解上面三个核心的API之后,接下来即可按如下步骤使用Core Image执行过滤。在使用Core Image过滤器之前,一定要为项目增加Core Image框架:

  • ①、创建CIContext对象。在iOS平台上右如下三种方式创建CIContext;

    • 第一种方式:创建基于CPU的CIContext对象;

      CIContext* ctx = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];
      
    • 第二种方式:创建基于GPU的CIContext对象;

      CIContext* ctx = [CIContext contextWithOptions:nil];
      
    • 第三种方式:创建基于OpenGL优化的CIContext对象,可获得实时性能;

      EAGLContext* eaglctx = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
      CIContext* ctx = [CIContext contextWithEAGLContext:eaglctx];
      

提示:采用GPU的CIContext将可以获取更好地性能,因此,一般建议创建基于GPU的CIContext,但基于GPU的CIContext对象无法跨应用访问。比如:应用打开UIImagePickerController选取照片进行美化,如果直接在UIImagePickerControllerDelegate的委托方法里调用CIContext对象进行处理,那么系统会自动将其降为基于CPU的,速度会变慢,因此,建议在委托方法中把照片保存下来,回到主类里调用CIContext进行处理。

  • ②、创建CIFilter过滤器。CIFilter提供了filterWithName:类方法来创建CIFilter对象。该方法需要传入过滤器的名字——Core Image提供了大量的过滤器,不同的过滤器有不同的名字;
  • ③、创建CIImage对象,该CIImahe将要作为过滤器处理的源图片。程序只要调用CIImage的不同方法,即可通过不同的媒介(如UIImage、图片文件、图片数据等)来创建CIImage对象;
  • ④、调用CIFilter的[filter setValue:beginImage forKey:@“inputImage”]方法为inputImage属性赋值,该属性用于指定该过滤器将要处理的源图片;
  • ⑤、根据需求,为不同的过滤器设置不同的过滤参数;
  • ⑥、调用CIFilter的outputImage属性获取该过滤器处理后的图片,程序返回的是CIImage对象;
  • ⑦、调用CIContext的不同方法将CIImage转换CGImageRef,或将该CIImage绘制到指定区域中。

例如:
这里写图片描述

RootViewController的声明部分

#import <UIKit/UIKit.h>
@interface RootViewController : UIViewController<UIImagePickerControllerDelegate,UINavigationControllerDelegate]] >
@property (nonatomic ,strong) UISlider* slider1;
@property (nonatomic ,strong) UISlider* slider2;
@property (nonatomic ,strong) UISlider* slider3;
@property (nonatomic ,strong) UISlider* slider4;
@property (nonatomic ,strong) UIImageView* iv;
@end

RootViewController的实现部分

#import "RootViewController.h"
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface RootViewController ()
@end
@implementation RootViewController{
    UIImagePickerController* imagePicker;
    CIContext* ctx;
    CIImage* beginImage;
    UIImage* resultImage;
    CIFilter* filter1;
    CIFilter* filter2;
    CIFilter* filter3;
    CIFilter* filter4;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.iv = [[UIImageView alloc]initWithFrame:CGRectMake(0, 20, SCREEN_WIDTH, 360)];
    [self.view addSubview:_iv];
    _iv.backgroundColor = [UIColor blackColor];
    //创建UIImagePickerController对象,用于选取照片库的照片
    imagePicker = [[UIImagePickerController alloc]init];
    //将UIImagePickerController的委托设为self
    imagePicker.delegate = self;

    UILabel* label1 = [[UILabel alloc]initWithFrame:CGRectMake(0, 400, 60, 30)];
    label1.text = @"模糊";
    label1.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label1];
    self.slider1 = [[UISlider alloc]initWithFrame:CGRectMake(60, 400, SCREEN_WIDTH - 80, 30)];
    [_slider1 addTarget:self action:@selector(sliderChange1:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:_slider1];

    UILabel* label2 = [[UILabel alloc]initWithFrame:CGRectMake(0, 430, 60, 30)];
    label2.text = @"鱼眼";
    label2.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label2];
    self.slider2 = [[UISlider alloc]initWithFrame:CGRectMake(60, 430, SCREEN_WIDTH - 80, 30)];
    [_slider2 addTarget:self action:@selector(sliderChange2:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:_slider2];

    UILabel* label3 = [[UILabel alloc]initWithFrame:CGRectMake(0, 460, 60, 30)];
    label3.text = @"色彩";
    label3.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label3];
    self.slider3 = [[UISlider alloc]initWithFrame:CGRectMake(60, 460, SCREEN_WIDTH - 80, 30)];
    [_slider3 addTarget:self action:@selector(sliderChange3:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:_slider3];

    UILabel* label4 = [[UILabel alloc]initWithFrame:CGRectMake(0, 490, 60, 30)];
    label4.text = @"像素";
    label4.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label4];
    self.slider4 = [[UISlider alloc]initWithFrame:CGRectMake(60, 490, SCREEN_WIDTH - 80, 30)];
    [_slider4 addTarget:self action:@selector(sliderChange4:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:_slider4];

    UIButton* button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button1.frame = CGRectMake(20, SCREEN_HEIGHT - 40, 60, 40);
    [button1 setTitle:@"重置" forState:UIControlStateNormal];
    [button1 addTarget:self action:@selector(reset:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button1];

    UIButton* button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button2.frame = CGRectMake(120, SCREEN_HEIGHT - 40, 60, 40);
    [button2 setTitle:@"照片" forState:UIControlStateNormal];
    [button2 addTarget:self action:@selector(load:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button2];

    UIButton* button3 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button3.frame = CGRectMake(220, SCREEN_HEIGHT - 40, 60, 40);
    [button3 setTitle:@"保存" forState:UIControlStateNormal];
    [button3 addTarget:self action:@selector(save:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button3];

    _slider1.minimumValue = 0;
    _slider1.maximumValue = 10;
    _slider2.minimumValue = -100;
    _slider2.maximumValue = 100;
    _slider3.minimumValue = -2 * M_PI;
    _slider3.maximumValue = 2 * M_PI;
    _slider4.minimumValue = 0;
    _slider4.maximumValue = 30;

    //用reset方法来初始化程序界面
    [self reset:nil];
    //打印所有的过滤器信息
    [self logAllFilters];

    //创建基于CPU的CIContext对象
    ctx = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];
    //创建基于GPU的CIContext对象
    ctx = [CIContext contextWithOptions:nil];
    //创建基于OpenGL优化的CIContext对象
    EAGLContext* eaglctx = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    ctx = [CIContext contextWithEAGLContext:eaglctx];
    //根据过滤器名来创建过滤器
    filter1 = [CIFilter filterWithName:@"CIGaussianBlur"];
    filter2 = [CIFilter filterWithName:@"CIBumpDistortion"];
    filter3 = [CIFilter filterWithName:@"CIHueAdjust"];
    filter4 = [CIFilter filterWithName:@"CIPixellate"];
}
//定义一个工具方法,对程序本身没有任何作用,仅仅用于查看系统内建的所有过滤器
- (void)logAllFilters{
    NSArray* properties = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
    NSLog(@"所有内建过滤器:\n%@",properties);
    for (NSString* filterName in properties) {
        CIFilter* fltr = [CIFilter filterWithName:filterName];
        //打印所有过滤器的默认属性
//        NSLog(@"====%@====\n%@",filterName,[fltr attributes]);
    }
}
//模糊
- (void)sliderChange1:(UISlider *)sender{
    //重设界面上其他UISlider的初始值
    self.slider2.value = 0;
    self.slider3.value = 0;
    self.slider4.value = 0;
    float slideValue = self.slider1.value;
    //设置该过滤器处理的原始照片
    [filter1 setValue:beginImage forKey:@"inputImage"];
    //为过滤器设置参数
    [filter1 setValue:[NSNumber numberWithFloat:slideValue] forKey:@"inputRadius"];
    //得到过滤器处理后的图片
    CIImage* outImage = [filter1 outputImage];
    CGImageRef tmp = [ctx createCGImage:outImage fromRect:[outImage extent]];
    //将处理后的图片转换为UIImage
    resultImage = [UIImage imageWithCGImage:tmp];
    CGImageRelease(tmp);
    //显示图片
    [self.iv setImage:resultImage];
}
- (void)sliderChange2:(UISlider *)sender{
    //重设界面上其他UISlider的初始值
    self.slider1.value = 0;
    self.slider3.value = 0;
    self.slider4.value = 0;
    float slideValue = self.slider2.value;
    //设置该过滤器处理的原始照片
    [filter2 setValue:beginImage forKey:@"inputImage"];
    //为过滤器设置参数
    [filter2 setValue:[CIVector vectorWithX:150 Y:240] forKey:@"inputCenter"];
    [filter2 setValue:[NSNumber numberWithFloat:150] forKey:@"inputRadius"];
    [filter2 setValue:[NSNumber numberWithFloat:slideValue] forKey:@"inputScale"];
    //得到过滤器处理后的图片
    CIImage* outImage = [filter2 outputImage];
    CGImageRef tmp = [ctx createCGImage:outImage fromRect:[outImage extent]];
    //将处理后的图片转换为UIImage
    resultImage = [UIImage imageWithCGImage:tmp];
    CGImageRelease(tmp);
    //显示图片
    [self.iv setImage:resultImage];
}
- (void)sliderChange3:(UISlider *)sender{
    //重设界面上其他UISlider的初始值
    self.slider1.value = 0;
    self.slider2.value = 0;
    self.slider4.value = 0;
    float slideValue = self.slider3.value;
    //设置该过滤器处理的原始照片
    [filter3 setValue:beginImage forKey:@"inputImage"];
    //为过滤器设置参数
    [filter3 setValue:[NSNumber numberWithFloat:slideValue] forKey:@"inputAngle"];
    //得到过滤器处理后的图片
    CIImage* outImage = [filter3 outputImage];
    CGImageRef tmp = [ctx createCGImage:outImage fromRect:[outImage extent]];
    //将处理后的图片转换为UIImage
    resultImage = [UIImage imageWithCGImage:tmp];
    CGImageRelease(tmp);
    //显示图片
    [self.iv setImage:resultImage];
}
- (void)sliderChange4:(UISlider *)sender{
    //重设界面上其他UISlider的初始值
    self.slider1.value = 0;
    self.slider2.value = 0;
    self.slider3.value = 0;
    float slideValue = self.slider4.value;
    //设置该过滤器处理的原始照片
    [filter4 setValue:beginImage forKey:@"inputImage"];
    //为过滤器设置参数
    [filter4 setValue:[CIVector vectorWithX:150 Y:240] forKey:@"inputCenter"];
    [filter4 setValue:[NSNumber numberWithFloat:slideValue] forKey:@"inputScale"];
    //得到过滤器处理后的图片
    CIImage* outImage = [filter4 outputImage];
    CGImageRef tmp = [ctx createCGImage:outImage fromRect:[outImage extent]];
    //将处理后的图片转换为UIImage
    resultImage = [UIImage imageWithCGImage:tmp];
    CGImageRelease(tmp);
    //显示图片
    [self.iv setImage:resultImage];
}
- (void)reset:(UIButton *)sender{
    //重设界面上UISlider的初始值
    self.slider1.value = 0;
    self.slider2.value = 0;
    self.slider3.value = 0;
    self.slider4.value = 0;
    //得到原始的图片路径
    NSString* filePath = [[NSBundle mainBundle]pathForResource:@"1" ofType:@"png"];
    //将图片路径转换为图片URL
    NSURL* fileUrl = [NSURL fileURLWithPath:filePath];
    //使用文件路径来创建UIImage,设置界面初始显示的图片
    resultImage = [UIImage imageWithContentsOfFile:filePath];
    self.iv.image = resultImage;
    //使用图片URL创建CIImage
    beginImage = [CIImage imageWithContentsOfURL:fileUrl];
}
- (void)load:(UIButton *)sender{
    //显示照片库
    [self presentViewController:imagePicker animated:YES completion:nil];
}
- (void)save:(UIButton *)sender{
    //调用UIImageWriteToSavedPhotosAlbum函数将结果图片保存到照片库
    UIImageWriteToSavedPhotosAlbum(resultImage, nil, nil, nil);
}
#pragma mark - UIImagePickerControllerDelegate
//当用户选中照片的某个方法时激发该方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    [self dismissViewControllerAnimated:YES completion:nil];
    //获取用户选中的照片
    UIImage* selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
    //根据已有的UIImage创建CIImage
    beginImage = [CIImage imageWithCGImage:selectedImage.CGImage];
    //显示用户选中的照片
    self.iv.image = selectedImage;
}
//当用户单击照片库的取消按钮时激发该方法
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    //隐藏照片库并退回原来的界面
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

七、动画

1、Core Animation动画基础

使用Core Animation创建动画不仅简单,而且具有更好地性能,原因有如下两个:

  • (1)、Core Animation动画在单独的线程中完成,不会阻塞主线程;
  • (2)、Core Animation动画只会重绘界面上变化的部分(局部刷新);
    Core Animation动画的核心是CALayer,每个UIView都有自己的CALayer,而且每个CALayer都可以不断地添加子CALayer,CALayer所在的CALayer被称为父CALayer,CALayer的这种组织方式被称为Layer Tree(各Layer之间的结构就像一棵树)。

除此之外,Core Animation动画还涉及如下API:

  • (1)、CAAnimation:它是所有动画类的基类,它实现了CAMediaTiming协议,提供了动画的持续时间、数独和重复计数等。CAAnimation还实现了CAAction协议,该协议为CALayer动画触发的动作提供标准化响应;
  • (2)、CATransition:CAAnimation的子类,CAAnimation可通过预置的过渡效果来控制CALayer层的过渡动画;
  • (3)、CAPropertyAnimation:它是CAAnimation的一个子类,它代表一个属性动画,可通过+animationWithKeyPath:类方法来创建属性动画实例(程序一般创建该子类的实例),该方法需要指定一个CALayer支持动画的属性,然后通过它的子类(CABasicAnimation、CAKeyframeAnimation)控制CALayer的动画属性慢慢地改变,即可实现CALayer动画;
  • (4)、CABasicAnimation:CAPropertyAnimation的子类,简单控制CALayer层的属性慢慢改变,从而实现动画效果。很多CALayer层的属性值的修改默认会执行这个动画类。比如大小、透明度、颜色等属性;
  • (5)、CAKeyframeAnimation:CAPropertyAnimation的子类,支持关键帧的属性动画,该动画最大的特点在于可通过values属性指定多个关键帧,通过多个关键帧可以指定动画的各阶段的关键帧;
  • (6)、CAAnimationGroup:它是CAAnimation的子类,用于将多个动画组合在一起执行。

2、使用CALayer

CALayer代表一个层,它提供了一个+layer类方法来创建CALayer层。

提示:所有的UIView都有一个默认的CALayer,通过UIView的layer属性即可访问UIView上的CALayer层。

使用CALayer的步骤非常简单,具体如下:

  1. 创建一个CALayer;
  2. 设置CALayer的contents属性即可设置该CALayer所显示的内容,该属性通常可指定一个CGImage,即代表CALayer将要显示的图片。如果需要自行绘制CALayer所显示的内容,可为CALayer指定delegate属性,该属性值应该是一个实现CALayerDelegate非正式协议的对象,重写该协议中的drawLayer:inContext:方法,即可完成CALayer的绘制。
  3. 为CALayer设置backgroundColor(背景色)、frame(设置大小和设置)、position(位置)、anchorPoint(锚点)、borderXxx(设置边框相关属性)、shadowXxx(设置阴影相关属性)等属性;
  4. 将该CALayer添加到父CALayer中即可。

与CALayer显示相关的还有如下几个常用属性:

  • (1)、contents:该属性控制CALayer显示的内容;
  • (2)、contentsRect:该属性控制CALayer的显示区域,其属性值是一个形如(0.0,0.0,1.0,1.0)的CGRect结构体,其中,1.0代表CALayer完整的宽和高;
  • (3)、contentsCenter:该属性控制CALayer的显示中心,其属性值是一个形如(0.0,0.0,1.0,1.0)的CGRect结构体,其中1.0代表CALayer完整的宽和高。通过该属性,可以把CALayer分成#字形网格,该属性指定的区域位置位于#字形中心。如果指定contentsCenter的contentsGravity属性为缩放模式,那么该CALayer被分成#字形的网格的上、下区域值进行水平缩放,#字形的网格的左、右区域只进行垂直缩放,中间区域进行两个方法的缩放,四个角则不进行缩放;
  • (4)、contentsGravity:该属性是一个NSString类型的常量值,用于控制CALayer中内容的缩放、对齐方式,它支持kCAGravityCenter等表示中心、上、下、左、右等对齐方式的属性值,也支持kCAGravityResizeXxx表示缩放的属性值;

例如:

这里写图片描述

#import "RootViewController.h"
#import <QuartzCore/QuartzCore.h>
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface RootViewController ()
@end
@implementation RootViewController
//圆角矩形
void CGContextAddRoundRect(CGContextRef c,CGFloat x1,CGFloat y1,CGFloat width,CGFloat height,CGFloat radius){
    //移动到左上角
    CGContextMoveToPoint(c, x1 + radius, y1);
    //添加一条连接到右上角的线段
    CGContextAddLineToPoint(c, x1 + width - radius, y1);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1, x1 + width, y1 + radius, radius);
    //添加一条连接到右下角的线段
    CGContextAddLineToPoint(c, x1 + width, y1 + height - radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1 + width, y1 + height, x1 + width - radius, y1 + height, radius);
    //添加一条连接到左下角的线段
    CGContextAddLineToPoint(c, x1 + radius, y1 + height);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1 + height, x1, y1 + height - radius, radius);
    //添加一条连接到左上角的线段
    CGContextAddLineToPoint(c, x1, y1 + radius);
    //添加一段圆弧
    CGContextAddArcToPoint(c, x1, y1, x1 + radius, y1, radius);
}
//该方法负责绘制多角形。
//n:该参数通常应设为奇数,控制绘制N角星;
//dx、dy:控制N角星的中心;
//size:控制N角星的大小,中心距角的距离;
void CGContextAddStar(CGContextRef c, NSInteger n, CGFloat dx, CGFloat dy, NSInteger size){
    CGFloat dig = 4 * M_PI / n;
    //移动到指定点
    CGContextMoveToPoint(c , dx, dy + size);
    for (int i = 0 ; i <= n; i ++ ) {
        CGFloat x = sin(i * dig);
        CGFloat y = cos(i * dig);
        //绘制从当前连接到指定点的线条
        CGContextAddLineToPoint(c, x * size + dx, y * size + dy);
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];
    //-------为UIView设置圆角边框-------
    //设置该视图控制器所显示的UIView上的CALayer的圆角半径
    self.view.layer.cornerRadius = 8;
    //设置该视图控制器所显示的UIView上的CALayer的边框宽度
    self.view.layer.borderWidth = 4;
    //设置该视图控制器所显示的UIView上的CALayer的边框颜色
    self.view.layer.borderColor = [UIColor redColor].CGColor;
    //-------创建简单的CALayer--------
    //创建一个CALayer对象
    CALayer* subLayer = [CALayer layer];
    //设置subLayer的背景颜色
    subLayer.backgroundColor = [UIColor magentaColor].CGColor;
    //设置sublayer的圆角半径
    subLayer.cornerRadius = 8;
    //设置subLayer的边框宽度
    subLayer.borderWidth = 2;
    //设置subLayer的背影色
    subLayer.borderColor = [UIColor blackColor].CGColor;
    //设置subLayer的阴影偏移(此处向右下角投下阴影)
    subLayer.shadowOffset = CGSizeMake(4, 5);
    //设置subLayer的阴影的模糊程度(该属性值越大,阴影越模糊)
    subLayer.shadowRadius = 1;
    //设置subLayer的阴影的颜色
    subLayer.shadowColor = [UIColor blackColor].CGColor;
    //设置subLayer的阴影的透明度
    subLayer.shadowOpacity = 0.8;
    //设置subLayer的大小和位置
    subLayer.frame = CGRectMake(30, 30, 120, 160);
    //将subLayer添加到该视图控制器所显示的UIView上的CALayer
    [self.view.layer addSublayer:subLayer];
    //在创建一个CALayer对象
    CALayer* subLayer2 = [CALayer layer];
    //设置该CALayer的边框、阴影、大小、位置等属性
    subLayer2.cornerRadius = 8;
    subLayer2.borderWidth = 2;
    subLayer2.borderColor = [UIColor blackColor].CGColor;
    subLayer2.shadowOffset = CGSizeMake(4, 5);
    subLayer2.shadowRadius = 1;
    subLayer2.shadowColor = [UIColor blackColor].CGColor;
    subLayer2.shadowOpacity = 0.8;
    subLayer2.masksToBounds = YES;
    subLayer2.frame = CGRectMake(170, 30, 120, 160);
    //将subLayer2添加到该试图控制器所显示的UIView上的CALayer
    [self.view.layer addSublayer:subLayer2];
    //-------使用CALayer显示图片-------
    //在创建一个CALayer对象
    CALayer* imageLayer = [CALayer layer];
    //设置imageLayer显示的图片
    imageLayer.contents = (id)([UIImage imageNamed:@"1.png"].CGImage);
    //设置imageLayer的大小和位置
    imageLayer.frame = subLayer2.bounds;
    //将imageLayer添加到subLayer2中
    [subLayer2 addSublayer:imageLayer];
    //-------自定义CALayer的绘制内容-------
    //再创建一个CALayer对象
    CALayer* customDrawn = [CALayer layer];
    //设置CALayer的委托对象,该委托对象负责该CALayer的绘制
    customDrawn.delegate = self;
    customDrawn.backgroundColor = [UIColor greenColor].CGColor;
    customDrawn.frame = CGRectMake(30, 200, 260, 220);
    customDrawn.shadowOffset = CGSizeMake(0, 3);
    customDrawn.shadowColor = [UIColor blackColor].CGColor;
    customDrawn.shadowOpacity = 0.8;
    customDrawn.cornerRadius = 10.0;
    customDrawn.borderWidth = 2.0;
    customDrawn.borderColor = [UIColor blackColor].CGColor;
    customDrawn.masksToBounds = YES;
    [self.view.layer addSublayer:customDrawn];
    //必须有这行,该代码用于通知CALayer调用delegate的drawLayer方法
    [customDrawn setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    UIColor* bgColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"1.png"]];
    CGContextSetFillColorWithColor(ctx, bgColor.CGColor);
    //填充一个椭圆
    CGContextFillEllipseInRect(ctx, CGRectMake(20, 20, 100, 100));
    CGContextAddRoundRect(ctx, 160, 20, 90, 60, 5);
    CGContextFillPath(ctx);
    CGContextSetRGBStrokeColor(ctx, 1, 1, 0, 1);
    CGContextStrokePath(ctx);
    CGContextAddStar(ctx, 5, 140, 150, 60);
    CGContextSetRGBFillColor(ctx, 0.5, 1, 1, 1);
    CGContextFillPath(ctx);
}
@end

3、使用CATransition控制过渡动画

CATransition通常用于通过CALayer控制UIView内子控件的过度画面,比如,删除子控件、添加子控件、切换两个子控件。

使用CATransition控制UIView内子控件的过度画面的步骤如下:

  1. 创建CATransition对象;
  2. 为CATransition设置type和subtype两个属性,其中,type指定动画类型,subtype指定动画移动方向;
  3. 如果不需要动画执行整个过程(就是只要动画执行到中间部分就停止),可以指定startProgress(动画的开始速度)、endProgress(动画的结束进度)属性;
  4. 调用UIView的layer属性的addAnimation:forKey:方法控制该UIView内子控件的过渡动画。addAnimation:forKey:方法的第一个参数为CAAnimation对象,第二个参数用于为该动画对象执行一个唯一标识。

提示:CATransition继承了CAAnimation,因此也支持指定CAAnimation的removedOnCompletion等属性;

CAAnimation提供了如下属性和方法:

  • (1)、BOOL removedOnCompletion;该属性用于指定该动画完成时是否从目标CALayer上删除该动画
  • (2)、CAMediaTimingFunction *timingFunction;该属性用于指定一个CAMediaTimingFunction对象,该对象负责控制动画边长的步长;
  • (3)、- (void)animationDidStart:(CAAnimation *)anim;该动画开始时将会回调该方法。开发者可以重写该方法执行自定义处理;
  • (4)、- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;该动画结束时将会回调该方法。开发者可以重写该方法执行自定义处理;

CATransition的type属性用于控制动画类型,它支持如下值(每个值代表一种类型的动画)。

  • ①、NSString * const kCATransitionFade;通过渐隐效果控制子组件的过渡。这是默认的属性值;
  • ②、NSString * const kCATransitionMoveIn;通过移入动画控制子组件的过渡;
  • ③、NSString * const kCATransitionPush;通过推入动画控制子组件的过渡;
  • ④、NSString * const kCATransitionReveal;通过揭开动画控制子组件的过渡;

除此之外,该属性该支持如下私有动画:

  • ①、cube:通过立方体旋转动画控制子组件的过渡;
  • ②、suckEffect:通过收缩动画(就像被吸入的效果)控制子组件的过渡;
  • ③、oglFlip:通过翻转动画控制子组件的过渡;
  • ④、rippleEffect:通过水波动画控制子组件的过渡;
  • ⑤、pageCurl:通过页面揭开动画控制子组件的过渡;
  • ⑥、pageUnCurl:通过放下页面动画控制子组件的过渡;
  • ⑦、cameraIrisHollowOpen:通过镜头打开动画控制子组件的过渡;
  • ⑧、cameraIrisHollowClose:通过镜头关闭动画控制子组件的过渡;

CATransition的subtype属性用于控制动画方向,它支持如下值:

  • ①、NSString * const kCATransitionFromRight;
  • ②、NSString * const kCATransitionFromLeft;
  • ③、NSString * const kCATransitionFromTop;
  • ④、NSString * const kCATransitionFromBottom;

提示:使用UIView的动画方式:
实际上,控制UIView内子控件的过渡还有另一种方式,通过UIView的+ (void)beginAnimations:(NSString )animationID context:(void )context;与 + (void)commitAnimations;方法控制,——如果子组件的过渡动画不是特别复杂,只需要实现一些简单的动画,即可通过这种方式控制。步骤如下:

  1. 调用UIView的+ (void)beginAnimations:(NSString )animationID context:(void )context;方法开始动画;
  2. 调用UIView的+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;设置动画类型、+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;方法设置动画的变化曲线。除此之外,UIView还提供了系列setAnimationXxx方法来设置动画的持续时间、延迟时间、重复次数等属性;
  3. 调用UIView的+ (void)commitAnimations;方法提交动画;

上面的+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;方法用于控制UIView的过渡动画的动画方式,它支持如下动画方式:

  • ①、UIViewAnimationTransitionNone:不适用动画;
  • ②、UIViewAnimationTransitionFlipFromLeft:指定从左边滑入的动画过渡方式;
  • ③、UIViewAnimationTransitionFlipFromRight:指定从右边滑出的动画过渡方式;
  • ④、UIViewAnimationTransitionCurlUp:指定“翻开书页”的动画过渡方式;
  • ⑤、UIViewAnimationTransitionCurlDown:指定“放下书页”的动画过渡方式;

方法+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;方法用于控制动画的变化曲线,也就是控制动画的变化速度,该方法支持如下几种变化速度:

  • ①、UIViewAnimationCurveEaseInOut:动画先比较缓慢,然后逐渐加快;
  • ②、UIViewAnimationCurveEaseIn:动画逐渐变慢;
  • ③、UIViewAnimationCurveEaseOut:动画逐渐加快;
  • ④、UIViewAnimationCurveLinear:匀速动画;

例如:
这里写图片描述

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView* magentaView = [[UIView alloc]initWithFrame:self.view.bounds];
    magentaView.backgroundColor = [UIColor magentaColor];
    [self.view addSubview:magentaView];
    UIView* grayView = [[UIView alloc]initWithFrame:self.view.bounds];
    grayView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:grayView];

    NSArray* bnTitleArray = [NSArray arrayWithObjects:@"添加",@"翻页",@"移入",@"揭开",@"立方体",@"收缩",@"翻转",@"水波", nil];
    NSMutableArray* bnArray = [[NSMutableArray alloc]init];
    //获取屏幕的内部高度
    CGFloat totalHeight = [UIScreen mainScreen].bounds.size.height;
    //创建8个按钮,并将按钮添加到NSMutableArray集合中
    for (int i = 0; i < 8; i ++) {
        UIButton* bn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [bn setTitle:[bnTitleArray objectAtIndex:i] forState:UIControlStateNormal];
        NSInteger row = i / 4;
        NSInteger col = i % 4;
        bn.frame = CGRectMake(5 + col * 80, totalHeight - (2 - row) * 45 - 20, 70, 35);
        [self.view addSubview:bn];
        [bnArray addObject:bn];
    }
    //为8个按钮分别绑定不同的时间处理方法
    [[bnArray objectAtIndex:0] addTarget:self action:@selector(add:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:1] addTarget:self action:@selector(curl:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:2] addTarget:self action:@selector(move:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:3] addTarget:self action:@selector(reveal:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:4] addTarget:self action:@selector(cube:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:5] addTarget:self action:@selector(suck:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:6] addTarget:self action:@selector(oglFlip:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:7] addTarget:self action:@selector(ripple:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)add:(UIButton *)sender{
    //开始执行动画
    [UIView beginAnimations:@"animation" context:nil];
    [UIView setAnimationDuration:1.0f];
    //控制UIView内过渡动画的类型
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view cache:YES];
    //设置动画的变化曲线
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView commitAnimations];
}
- (void)curl:(UIButton *)sender{
    //开始执行动画
    [UIView beginAnimations:@"animation" context:nil];
    [UIView setAnimationDuration:1.0f];
    //控制UIView内过渡动画的类型
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];
    //设置动画的变化曲线
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    //交换视图控制器所显示的UIView中两个子控件的位置
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
    [UIView commitAnimations];
}
- (void)move:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = 2.0f;
    //通过移入动画控制子组件的过渡
    transition.type = kCATransitionMoveIn;
    //指定动画方向,从左向右
    transition.subtype = kCATransitionFromLeft;
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
- (void)reveal:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = 2.0f;
    //通过揭开动画控制子组件的过渡
    transition.type = kCATransitionReveal;
    //指定动画方向,从上向下
    transition.subtype = kCATransitionFromTop;
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
- (void)cube:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = 2.0f;
    //通过立方体旋转动画控制子组件的过渡
    transition.type = @"cube";
    //指定动画方向,从左到右
    transition.subtype = kCATransitionFromLeft;
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
- (void)suck:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = 2.0f;
    //通过收缩动画(就像被吸入的效果)控制子组件的过渡 该动画与动画方向无关
    transition.type = @"suckEffect";
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
- (void)oglFlip:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = 2.0f;
    //通过翻转动画控制子组件的过渡;
    transition.type = @"oglFlip";
    //指定动画方向,从下向上
    transition.subtype = kCATransitionFromBottom;
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
- (void)ripple:(UIButton *)sender{
    //开始执行动画
    CATransition* transition = [CATransition animation];
    transition.duration = .5f;
    //通过水波动画控制子组件的过渡 该动画与动画方向无关
    transition.type = @"rippleEffect";
    [self.view.layer addAnimation:transition forKey:@"animation"];
    [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}

4、使用属性动画(CAPropertyAnimation)

属性动画由CAPropertyAnimation代表,该对象用于控制CALayer的动画属性(所有支持数值型属性值的属性几乎都可作为动画属性)持续改变,当CALayer的动画属性持续改变时,CALayer的外观就会持续改变——用户看上去就变成了动画。

CAPropertyAnimation提供了如下类方法来创建属性动画:

+ (id)animationWithKeyPath:(NSString *)path;该方法仅需要一个参数,该参数只是一个字符串类型的值,指定CALayer的动画属性名,设置该属性动画控制CALayer的哪个动画属性持续改变。

除此之外,CAPropertyAnimation还支持如下属性:

  • (1)、keyPath:该属性值返回创建CAPropertyAnimation时指定的参数;
  • (2)、additive:该属性指定该属性动画是否以当前动画效果为基础;
  • (3)、cumulative:该属性指定动画是否为累加效果;
  • (4)、valueFunction:该属性值是一个CAValueFunction对象,该对象负责对属性改变的插值计算。系统已经提供了默认的插值计算方式,因此一般无需指定该属性。

如果要控制CALayer的位移动画,直接使用属性动画控制CALayer的postion持续改变即可。如果要控制该CALayer的缩放、旋转、斜切等效果,则需要控制如下属性。

  • (5)、affineTransform:该属性值指定一个CGAffineTransform对象,该对象代表对CALayer执行X、Y两个维度(也就是平面)上的旋转、缩放、位移、斜切、镜像等变换矩阵;
  • (6)、transform:该属性值指定一个CATransform3D对象,该对象代表对CALayer指定X、Y、Z三个维度(也就是三维空间)中的旋转、缩放、位移、斜切、镜像等变换矩阵。

一般来说,可使用Core Animation提供了如下属性来创建三维变换矩阵:

  • (1)、bool CATransform3DIsIdentity (CATransform3D t);判断t矩阵是否为单位矩阵;
  • (2)、bool CATransform3DEqualToTransform (CATransform3D a, CATransform3D b);判断连个变换矩阵是否相等;
  • (3)、CATransform3D CATransform3DMakeTranslation (CGFloat tx, CGFloat ty, CGFloat tz);创建在X方向上移动tx、Y方向上移动ty、Z方向上移动tz的变换矩阵;
  • (4)、CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz);创建在X方向上缩放sx、Y方向上缩放sy、Z方向上缩放sz的变换矩阵;
  • (5)、CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z);创建基于指定旋转轴旋转angle弧度的变换。其中参数x、y、z的值用于确定旋转轴的方向。比如(1,0,0)指定旋转轴为X轴,(1,1,0)指定以X轴、Y周夹角的中线为旋转轴;
  • (6)、CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz);以已有t变换矩阵为基础进行位移变换;
  • (7)、CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz);以已有t变换矩阵为基础执行缩放变换;
  • (8)、CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z);以已有的t变换为基础执行旋转变换;
  • (9)、CATransform3D CATransform3DConcat (CATransform3D a, CATransform3D b);对a变换矩阵执行累加;
  • (10)、CATransform3D CATransform3DInvert (CATransform3D t);对已有的t变换执行反转;
  • (11)、CATransform3D CATransform3DMakeAffineTransform (CGAffineTransform m);将CGAffineTransform矩阵包装成CATransform3D变换矩阵,该CATransform3D也只有X、Y维度的变换;
  • (12)、bool CATransform3DIsAffine (CATransform3D t);如果t变换只是一个CGAffineTransform矩阵,则该函数返回YES;
  • (13)、CGAffineTransform CATransform3DGetAffineTransform (CATransform3D t);获取t变换矩阵所包含的CGAffineTransform变换矩阵。

使用属性动画控制CALayer的执行动画创建属性动画:

  • ①、利用animationWithKeyPath类方法创建属性动画;
  • ②、如果使用CABasicAnimation属性动画,则可指定fromValue、toValue两个属性值,其中,formValue指定动画属性开始时的属性值,toValue指定动画属性结束时的属性值;如果使用CAKeyframeAnimation属性动画,则指定values属性值,该属性值是一个NSArray属性,其中第一个元素指定动画属性开始时的属性值,toValue指定动画属性结束时的属性值,其他数组元素指定动画变化过程中的属性值。

提示:CABasicAnimation,CAKeyframeAnimation都继承了CAPropertyAnimation,他们都是属性动画,只是CABasicAnimation只能指定动画属性的开始值和结束值,该CALayer的动画属性就由开始值变化到结束值;而CAKeyframeAnimation则可为动画属性指定多个值,该CALayer的动画属性就从values的第一个属性值开始,依次经历每个属性值,知道变成最后一个属性值。

  • ③、调用CALayer的addAnimation:forKey:添加动画即可;

CALayer为动画支持提供了如下方法:

  • (1)、- (void)addAnimation:(CAAnimation )anim forKey:(NSString )key;为该CALayer添加一个动画,第二个参数为该动画指定key(相当于该动画的唯一标识,这样保证每个CALayer可绑定多个动画对象);
  • (2)、- (CAAnimation )animationForKey:(NSString )key;控制该CALayer执行指定key所对应的动画;
  • (3)、- (void)removeAllAnimations;删除CALayer上添加的所有动画;
  • (4)、- (void)removeAnimationForKey:(NSString *)key;根据key删除该CALayer上指定的动画;
  • (5)、- (NSArray *)animationKeys;获取CALayer上添加的所有动画key所组成的数组;

例如:

#import "RootViewController.h"
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface RootViewController ()
{
    CALayer* imageLayer;
}
@end
@implementation RootViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    //创建一个CALayer对象
    imageLayer = [CALayer layer];
    //设置该CALayer的边框、大小、位置等属性
    imageLayer.cornerRadius = 6;
    imageLayer.borderWidth = 1;
    imageLayer.borderColor = [UIColor blackColor].CGColor;
    imageLayer.masksToBounds = YES;
    imageLayer.frame = CGRectMake(30, 30, 100, 135);
    //设置imageLayer显示的图片
    imageLayer.contents = (id)[UIImage imageNamed:@"1.png"];
    [self.view.layer addSublayer:imageLayer];
    NSArray* bnTitleArray = [NSArray arrayWithObjects:@"位移",@"旋转",@"缩放",@"动画组", nil];
    //获取屏幕的内部高度
    CGFloat totalHeight = [UIScreen mainScreen].bounds.size.height;
    NSMutableArray* bnArray = [[NSMutableArray alloc]init];
    //采用循环创建4个按钮
    for (int i = 0; i < 4; i ++) {
        UIButton* bn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [bn setTitle:[bnTitleArray objectAtIndex:i] forState:UIControlStateNormal];
        bn.frame = CGRectMake(5 + i * 80, totalHeight - 45 - 20, 70, 35);
        [self.view addSubview:bn];
        [bnArray addObject:bn];
    }
    //为4个按钮分别绑定不同的事件处理方法
    [[bnArray objectAtIndex:0] addTarget:self action:@selector(move:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:1] addTarget:self action:@selector(rotate:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:2] addTarget:self action:@selector(scale:) forControlEvents:UIControlEventTouchUpInside];
    [[bnArray objectAtIndex:3] addTarget:self action:@selector(group:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)move:(UIButton *)sender{
    //获取imageLayer的位置
    CGPoint fromPoint = imageLayer.position;
    CGPoint toPoint = CGPointMake(fromPoint.x + 80, fromPoint.y);
    //创建不断改变CALayer的position属性的属性动画
    CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"positon"];
    //设置动画开始的属性值
    anim.fromValue = [NSValue valueWithCGPoint:fromPoint];
    //设置动画结束的属性值
    anim.toValue = [NSValue valueWithCGPoint:toPoint];
    anim.duration = 0.5;
    imageLayer.position = toPoint;
    anim.removedOnCompletion = YES;
    [imageLayer addAnimation:anim forKey:nil];
}
- (void)rotate:(UIButton *)sender{
    //创建不断改变CALayer的transform属性的属性动画
    CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    CATransform3D fromValue = imageLayer.transform;
    //设置动画开始的属性值
    anim.fromValue = [NSValue valueWithCATransform3D:fromValue];
    //绕X轴旋转180度
//    CATransform3D toValue = CATransform3DRotate(fromValue, M_PI, 1, 0, 0);
    //绕Y轴旋转180度
//    CATransform3D toValue = CATransform3DRotate(fromValue, M_PI, 0, 1, 0);
    //绕Z轴旋转180度
    CATransform3D toValue = CATransform3DRotate(fromValue, M_PI, 0, 0, 1);
    anim.toValue = [NSValue valueWithCATransform3D:toValue];
    anim.duration = 0.5;
    imageLayer.transform = toValue;
    anim.removedOnCompletion = YES;
    [imageLayer addAnimation:anim forKey:nil];
}
- (void)scale:(UIButton *)sender{
    //创建不断改变CALayer的transform属性的属性动画
    CAKeyframeAnimation* anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    //设置CAKeyframeAnimation控制transform属性依次经过的属性值
    anim.values = [NSArray arrayWithObjects:[NSValue valueWithCATransform3D:imageLayer.transform],[NSValue valueWithCATransform3D:CATransform3DScale(imageLayer.transform, 0.2, 0.2, 1)],[NSValue valueWithCATransform3D:CATransform3DScale(imageLayer.transform, 2, 2, 1)],[NSValue valueWithCATransform3D:imageLayer.transform], nil];
    anim.duration = 5;
    anim.removedOnCompletion = YES;
    [imageLayer addAnimation:anim forKey:nil];
}
- (void)group:(UIButton *)sender{
    CGPoint fromPoint = imageLayer.position;
    CGPoint toPoint = CGPointMake(fromPoint.x + 80, fromPoint.y);
    //创建不断改变CALayer的position属性的属性动画
    CABasicAnimation* moveAnim = [CABasicAnimation animationWithKeyPath:@"positon"];
    //设置动画开始的属性值
    moveAnim.fromValue = [NSValue valueWithCGPoint:fromPoint];
    //设置动画结束的属性值
    moveAnim.toValue = [NSValue valueWithCGPoint:toPoint];
    moveAnim.duration = 6;
    imageLayer.position = toPoint;
    moveAnim.removedOnCompletion = YES;
    //--------------------
    //创建不断改变CALayer的transform属性的属性动画
    CABasicAnimation* transformAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
    CATransform3D fromValue = imageLayer.transform;
    //设置动画开始的属性值
    transformAnim.fromValue = [NSValue valueWithCATransform3D:fromValue];
    //创建在X、Y两个方向上缩放为0.5的变换矩阵
    CATransform3D scaleValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
    //绕Z轴旋转180度的变换矩阵
    CATransform3D rotateValue = CATransform3DRotate(fromValue, M_PI, 0, 0, 1);
    //计算两个变换矩阵的和
    CATransform3D toValue = CATransform3DConcat(scaleValue, rotateValue);
    //设置动画结束的属性值
    transformAnim.toValue = [NSValue valueWithCATransform3D:toValue];
    //动画效果累加
    transformAnim.cumulative = YES;
    //动画重复执行次数,旋转360度
    transformAnim.repeatCount = 2;
    transformAnim.duration = 6;
    //位移、缩放、旋转组合起来执行
    CAAnimationGroup* animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:moveAnim,transformAnim, nil];
    animGroup.duration = 6;
    [imageLayer addAnimation:animGroup forKey:nil];
}
@end

5、控制移动路径

对CAKeyframeAnimation而言,它除了可通过values属性指定动画过程中的多个值之外,还可通过path属性指定CALayer的移动路径,该属性值为CGPathRef,通过这种方式可控制CALayer按我们指定的轨迹移动,从而指定更细致的动画;

实例:绕圈游动的小鱼

#import "RootViewController.h"
#define SCREEN_WIDTH [[UIScreen mainScreen]bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen]bounds].size.height
@interface RootViewController ()
{
    CALayer* fishLayer;
    NSInteger fishFrame;
    NSTimer* timer;
    //定义NSMutableArray装鱼的10个动作帧
    NSMutableArray* fishFrameArray;
}
@end
@implementation RootViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //创建CALayer对象
    CALayer* bg = [CALayer layer];
    //设置背景颜色
    bg.contents = (id)[UIImage imageNamed:@"1.png"].CGImage;
    bg.contentsGravity = kCAGravityCenter;
    bg.frame = self.view.bounds;
    [self.view.layer addSublayer:bg];
    fishFrameArray = [[NSMutableArray alloc]init];
    for (int i = 0; i < 10; i ++) {
        [fishFrameArray addObject:[UIImage imageNamed:[NSString stringWithFormat:@"DOVE %d.png",i+1]]];
    }
    //创建定时器控制小鱼的动画帧的改变
    timer = [NSTimer scheduledTimerWithTimeInterval:0.15 target:self selector:@selector(change) userInfo:nil repeats:YES];
    //创建CALayer
    fishLayer = [CALayer layer];
    //设置CALayer显示内容的不缩放,直接显示在中间
    fishLayer.contentsGravity = kCAGravityCenter;
    fishLayer.frame = CGRectMake(128, 6, 90, 40);
    [self.view.layer addSublayer:fishLayer];
    //创建一个按钮 通过该按钮触发小鱼的游动
    UIButton* bn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    bn.frame = CGRectMake(10, 10, 60, 35);
    [bn setTitle:@"开始" forState:UIControlStateNormal];
    [bn addTarget:self action:@selector(start:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:bn];
}
- (void)start:(UIButton *)sender{
    //创建对CALayer的position属性执行控制的属性动画
    CAKeyframeAnimation* anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //创建路径
    CGMutablePathRef movePath = CGPathCreateMutable();
    //添加一条圆形的路径
    CGPathAddArc(movePath, nil, 170, 175, 150, -M_PI / 2, M_PI * 3 / 2, YES);
    //设置anim动画的移动路径
    anim.path = movePath;
    //创建对CALayer的transform属性执行控制的属性动画
    CAKeyframeAnimation* anim2 = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    //指定关键帧动画的三个关键值,分别是不旋转,旋转180度,旋转360度
    anim2.values = [NSArray arrayWithObjects:[NSValue valueWithCATransform3D:CATransform3DIdentity],[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 0, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeRotation(2 * M_PI, 0, 0, 1)], nil];
    //使用动画组来组合两个动画
    CAAnimationGroup* animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:anim,anim2, nil];
    animGroup.repeatCount = 20;
    animGroup.duration = 1;
    //为fishLayer添加动画
    [fishLayer addAnimation:anim forKey:@"move"];
}
- (void)change{
    fishLayer.contents = (id)[[fishFrameArray objectAtIndex:fishFrame ++ % 10]CGImage];
}
@end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoxiaobukuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值