环形进度条的简单实现,值得一看新葡京8455

作者:新闻中心

··~~因为工作原因需要对动画有所了解,由于之前用的很少,这方面的知识很匮乏,所以借此机会深入的了解了下关于动画方面的知识,由于没有找到好的动画效果,就高仿了一个用CAKeyframeAnimation实现的过山车动画~呜呜呜

新葡京8455 11505202465905.jpg

动态果图:

  • CAShapeLayer
  • CAGradientLayer
  • CAKeyframeAnimation
小牢骚

好久没有记录文章了,近期在学习前端的相关知识,也没有好好的静下心来整理东西,这次准备把之前自己用到的东西做一个总结,方便以后自己查阅.

iOS 绘制颜色渐变圆环

片头曲 曲曲曲曲曲曲曲曲曲曲曲曲曲

渐变的天空背景色使用CAGradientLayer类来生成两种或更多的渐变平滑的颜色,草坪和轨道使用CAShapeLayer配合UIBezierPath完成,云朵、大地和大树可以通过CALayer设置contents属性来实现(注:如果给CALayer设置color或imaeg属性时,需要将color转换为CGColor,image转换为CGImage,不然图片或颜色不显示---),云朵和过山车的动画可以使用CAKeyframeAnimation来实现,是不是看起来好简单~~~~

---------------------------------下面是每个类的功能介绍---------------------------------

正文

这次记录使用CALayer创建一个环形进度条的view,加上简单的动画,项目中刚好有使用到。效果图:

新葡京8455 2porgressView.gif

要点: 主要使用layer的mask属性,mask属性可以让layer只显示遮罩层上面的路径

本文主要介绍一种绘制颜色渐变的进度圆环. 先上效果图:

6.常用layer

CAShapeLayer

  • 1、CAShapeLayer是一种特殊的层,可以在上面渲染图形。
  • 2、CAShapeLayer继承自CALayer,可使用CALayer的所有属性。
  • 3、CAShapeLayer需要和贝塞尔曲线配合使用才有意义,贝塞尔曲线为其提供渲染的图形。
  • 4、使用CAShapeLayer与贝塞尔曲线可以实现不再view的drawRect方法中画出一些想要的图形。

分析

在进度值为0的时候,有一个黄色的底色,然后进度条的颜色是渐变的,底色环形可以通过给view的layer添加一个带环形路径的CAShapeLayer作为mask,渐变色可以使用CAGrandientLayer创建一个渐变图层,然后创建一个带环形路径的CAShapeLayer,将其作为渐变图层的mask,这样就能达到预期的效果

实现思路:

6.1 CAShapeLayer

CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:

  • 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
  • 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  • 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core - Graphics的普通CALayer一样被剪裁掉
  • 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。

画一个火柴人

#import "DrawingView.h"
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  //create path
  UIBezierPath *path = [[UIBezierPath alloc] init];
  [path moveToPoint:CGPointMake(175, 100)];

  [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
  [path moveToPoint:CGPointMake(150, 125)];
  [path addLineToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(125, 225)];
  [path moveToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(175, 225)];
  [path moveToPoint:CGPointMake(100, 150)];
  [path addLineToPoint:CGPointMake(200, 150)];

  //create shape layer
  CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  shapeLayer.strokeColor = [UIColor redColor].CGColor;
  shapeLayer.fillColor = [UIColor clearColor].CGColor;
  shapeLayer.lineWidth = 5;
  shapeLayer.lineJoin = kCALineJoinRound;
  shapeLayer.lineCap = kCALineCapRound;
  shapeLayer.path = path.CGPath;
  //add it to our view
  [self.containerView.layer addSublayer:shapeLayer];
}
@end
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类, CAShapeLayer相当于使用画笔直接在图层上绘制想要的图片,通过GPU来渲染图形,如果使用drawRect来绘制图形是调用CoreGraphics框架中得方法,占用CPU,消耗性能大。使用CAShapeLayer你可以指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:
  • 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
  • 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  • 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉
  • 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
 //第一块草坪 self.shapeLayer = [CAShapeLayer layer]; //通过贝塞尔曲线绘制路径 UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(0, kScreenWidth - 20)];//设置曲线的起点 [path addLineToPoint:CGPointMake(0, kScreenWidth - 100)];//连线到某点 [path addQuadCurveToPoint:CGPointMake(kScreenHeight / 3.0, kScreenWidth - 20) controlPoint:CGPointMake(kScreenHeight / 6.0, kScreenWidth - 100)];//设置终点和控制点,控制点越小绘制的图形越大越高 self.shapeLayer.path = path.CGPath; self.shapeLayer.fillColor = [UIColor colorWithRed:82.0/255.0 green:177.0/255.0 blue:44.0/255.0 alpha:1.0].CGColor;//设置填充色 [self.backV.layer addSublayer:self.shapeLayer]; //第二块草坪 CAShapeLayer *shapeLayer2 = [CAShapeLayer layer]; UIBezierPath *path2 = [UIBezierPath bezierPath]; [path2 moveToPoint:CGPointMake(0, kScreenWidth - 20)]; [path2 addQuadCurveToPoint:CGPointMake(kScreenHeight, kScreenWidth - 60) controlPoint:CGPointMake(kScreenHeight / 2.0, kScreenWidth - 150)]; [path2 addLineToPoint:CGPointMake(kScreenHeight, kScreenWidth - 20)]; shapeLayer2.path = path2.CGPath; shapeLayer2.fillColor = [UIColor colorWithRed:92.0/255.0 green:195.0/255.0 blue:52.0/255.0 alpha:1.0].CGColor; [self.backV.layer addSublayer:shapeLayer2];

新葡京8455 3006tNbRwgw1f53ba1snf4j30ij0agtc7.jpg

相关知识点

  • CAGradientLayer创建渐变色layer,他是继承自CALayer的一个用于绘制多色图层的子类,主要用于设置的属性值为
    • @property(nullable, copy) NSArray *colors;设置需要加载的颜色数组(CGColorRef类型)
    • @property(nullable, copy) NSArray<NSNumber *> *locations;不同颜色值的分布分割点(NSNumber类型)
    • @property CGPoint startPoint;
    • @property CGPoint endPoint;

具体的相关详细解析这里引用一篇文章:CAGradientLayer详细使用本文中使用到的只是简单的创建三个颜色值的图层

CAGradientLayer *layer = [CAGradientLayer layer]; layer.frame = self.bounds; NSMutableArray *muColors = [NSMutableArray array];//颜色数组,放置CGColorref if (self.colors) { [self.colors enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[UIColor class]]) { UIColor *color = (UIColor *)obj; [muColors addObject:color.CGColor]; } }]; }else { //设置两组白色 [muColors addObject:[UIColor whiteColor].CGColor]; [muColors addObject:[UIColor whiteColor].CGColor]; } layer.colors = muColors; if (!self.locations) { //随便设置两组分割点 self.locations = [NSMutableArray arrayWithObjects:@0.1, @0.9,nil]; } layer.locations = self.locations; return layer;

代码中的self.colors为包含UICololr的颜色数组,self.locations为包含NSNumber值的数组颜色值设为@[COLOR(100, 150, 0, 1),COLOR(0, 150, 130, 1),COLOR(60, 0, 190, 1)];location设置为@[@0.1,0.5,0.9],效果如下

新葡京8455 4渐变色图层.png

  • CAShaprLayer 一个很强大的CALayer子类,由于其可以添加path路径,因此可以绘制出很多复杂的图形。这里也引用一篇之前看过的不错的博客:关于CAShapeLayer和UIBzierPath的使用本文中主要使用CAShapeLayer创建一个环形路径的图层
#define DEGREES_TO_ANGLE (M_PI *  / 180.0) // 将角度转为弧度CAShapeLayer *layer = [CAShapeLayer layer]; layer.frame = self.bounds; UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2) radius:self.bounds.size.width / 2.5 startAngle:DEGREES_TO_ANGLE endAngle:DEGREES_TO_ANGLE clockwise:YES]; //注意这里的半径,应该为宽度1/2-linewidth/2 layer.lineWidth = self.lineWidth; //线的宽度 layer.path = path.CGPath; layer.fillColor = [UIColor clearColor].CGColor; //设置填充颜色 layer.strokeColor = [UIColor blackColor].CGColor; // 设置画笔颜色 layer.lineCap = kCALineCapRound; // 设置线为圆角

其中需要注意的点:

  • 这里的坐标系和数学中的坐标系是反的,简单的理解就是-90°等价数学坐标系中的90°,90°等价数学坐标系中的270°,所以如果希望进度条从顶部按照顺时针走,那么在设置UIBezierPath画圆弧的时候就应该为clockwise:yes,startangle:(-90°对应的弧度),endangle:(270°对应的弧度),这样就是生成按照顺时针方向从-90°的点到270°的点之间的路径。如下图片中不同的角度对应的效果图:

    新葡京8455 5多种弧度的圆弧.png

  • 在选择半径时,如果fram宽度为100,希望绘制一个宽度20紧靠边缘的环形,那么半径应该是50-20/2,并不是50-20。

CAShapeLayer UIBezierPath CAGradientLayer

圆角

我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形:

//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;

// 蒙板
self.contentView.layer.mask = shapeLayer;

我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把CAShapeLayer作为视图的宿主图层,而不是添加一个子视图

CAGradientLayer

  • CAGradientLayer是CALayer图层类的子类,用于处理渐变色的层结构。
  • CAGradientLayer的渐变色可以做隐式动画。
  • CAGradientLayer和CAShapeLayer配合使用可实现复杂效果。
  • CAGradientLayer可以用作PNG的遮罩效果。
  • CAGradientLayer 坐标系统

新葡京8455 61339729-c2ec801415d3aca9.png

  • CAGradientLayer的坐标系统是从坐标到绘制的矩形。
  • CAGradientLayer的frame值的size不为正方形的话,坐标系统会被拉伸。
  • CAGradientLayer的startPoint与endPoint会直接影响颜色的绘制方向。
  • CAGradientLayer的颜色分割点是以0~1的比例来计算的,颜色分割点为渐变色开始或终止的地方。
  • colors:渐变颜色数组
  • locations:渐变颜色的区间分布,locations的数组长度和colors一致。这个属性可不设,默认是nil,系统会平均分布颜色如果有特定需要可设置,数组设置为0 ~ 1之间单调递增。
  • startPoint:映射locations中起始位置,用单位向量表示。比如表示从左上角开始变化。默认值是:。
  • endPoint:映射locations中结束位置,用单位向量表示。比如表示到右下角变化结束。默认值是:。
  • type:默认值是kCAGradientLayerAxial,表示按像素均匀变化。

使用CAGradientLayer来实现了项目中天空背景的绘制,效果图:

新葡京8455 7006tNbRwgw1f53ax28scyj30ij09w74d.jpg

 //初始化 背景 self.CAGLayer = [CAGradientLayer layer]; self.CAGLayer.frame = self.backV.bounds; //设置渐变的颜色 self.CAGLayer.colors = @[[UIColor colorWithRed:178.0/255.0 green:226.0/255.0 blue:248.0/255.0 alpha:1.0].CGColor, [UIColor colorWithRed:232.0/255.0 green:244.0/255.0 blue:193.0/255.0 alpha:1.0].CGColor]; //设置渐变的方向 自左上到右下 self.CAGLayer.startPoint = CGPointMake; self.CAGLayer.endPoint = CGPointMake; [self.backV.layer addSublayer: self.CAGLayer];
  • CAKeyframeAnimation又称关键帧动画
  • CAKeyframeAnimation是抽象类CAPRopertyAnimation的子类,可以直接使用
  • 通过values与path两个属性指定动画属性

代码解析

progressView.h

typedef NS_ENUM (NSInteger, WJStrokeType)//动画方式{ WJStrokeTypeStart, //开始 WJStrokeTypeEnd //结束};//动画结束回调typedef void(^ProgressEnd)();@interface WJProgressView : UIView/** * 线宽 */@property(nonatomic,assign)CGFloat lineWidth;/** * 背景颜色 */@property(nonatomic,strong)UIColor *bgColor;/** * 渐变色颜色组 */@property(nonatomic,strong)NSArray *colors;/** * 颜色分割位置数组 */@property(nonatomic,strong)NSArray *locations;/** * 隐藏百分比label */- hidePercent;/** * 设置创建图层 */- startProress;/** * 更新progress * * @param value progress Value */- reloadValue:value;/** * 进度动画 * * @param beginValue 开始value * @param endValue 结束value * @param duration 动画持续时间 * @param type 动画类型 * @param endBlock 动画结束回调 */- setprogressFromValue:beginValue endValue:endValue duration:duration strokeType:(WJStrokeType)type end:(ProgressEnd)endBlock;

.m

- setprogressFromValue:beginValue endValue:endValue duration:time strokeType:(WJStrokeType)type end:(ProgressEnd)endBlock{ if (beginValue >= 1.0) { endBlock(); return; } _endValue = endValue; self.animationEnd = endBlock; NSString *animationName = @"strokeStart"; if  { animationName = @"strokeEnd"; } CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:animationName]; pathAnimation.duration = time; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; pathAnimation.fromValue = [NSNumber numberWithFloat:beginValue]; pathAnimation.toValue = [NSNumber numberWithFloat:endValue]; pathAnimation.autoreverses = NO; pathAnimation.delegate = self; pathAnimation.removedOnCompletion = NO; pathAnimation.fillMode = kCAFillModeForwards; [self.colorMaskLayer addAnimation:pathAnimation forKey:@"strokeAnimation"]; [self creatTimerFromValue:beginValue endValue:endValue duration:time];}

对于cCAShapeLayer,stokeEnd和strokestart属性都直接动画操作。在动画过程中关于中间percentLabel的显示,使用定时器设置时间间隔实现变化。

/** 创建定时器在layer animation过程中改变percent @param beginValue 初始值 @param endValue 结束值 @param time 时间间隔 */- creatTimerFromValue:beginValue endValue:endValue duration:time{ __block CGFloat beginPercent = beginValue; CGFloat timeInterval = 0.1; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(self.timer, dispatch_walltime, timeInterval*NSEC_PER_SEC, 0); dispatch_source_set_event_handler(self.timer, ^{ dispatch_async(dispatch_get_main_queue(), ^{ CGFloat difference = endValue - beginValue; CGFloat addValue = difference/time/(1/timeInterval); beginPercent  = addValue; if (beginPercent >= endValue) beginPercent = endValue; if (beginPercent >= 1.0) beginPercent = 1.0f; [self reloadPercentLabelValue:beginPercent]; }); }); dispatch_resume;}

注意点:dispatch_source_t 的创建定一个属性值来保存。不要直接在方法中创建。在动画结束后对timer进行销毁处理。

- animationDidStop:(CAAnimation *)anim finished:flag{ if (self.animationEnd) self.animationEnd(); [self.colorMaskLayer removeAnimationForKey:@"strokeAnimation"]; self.colorMaskLayer.strokeEnd = _endValue; if (self.timer) { dispatch_source_cancel(self.timer); self.timer = nil; }}

End demo地址

UIBezierPath 用来绘制圆环路径;

6.2 CATextLayer

如果你想在一个图层里面显示文字,完全可以借助图层代理直接将字符串使用Core Graphics写入图层的内容(这就是UILabel的精髓)。

CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。而CATextLayer使用了Core text,并且渲染得非常快。

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *labelView;

@end

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.foregroundColor = [UIColor blackColor].CGColor;
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  textLayer.font = fontRef;
  textLayer.fontSize = font.pointSize;
  CGFontRelease(fontRef);

  //choose some text
  NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing  elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar  leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel  fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet  lobortis";

  //set layer text
  textLayer.string = text;
}
@end

如果你仔细看这个文本,你会发现一个奇怪的地方:这些文本有一些像素化了。这是因为并没有以Retina的方式渲染,contentScale属性,用来决定图层内容应该以怎样的分辨率来渲染。contentsScale并不关心屏幕的拉伸因素而总是默认为1.0。如果我们想以Retina的质量来显示文字,我们就得手动地设置CATextLayer的contentsScale属性,如下:

textLayer.contentsScale = [UIScreen mainScreen].scale;

CATextLayer的font属性不是一个UIFont类型,而是一个CFTypeRef类型。这样可以根据你的具体需要来决定字体属性应该是用CGFontRef类型还是CTFontRef类型(Core Text字体)。同时字体大小也是用fontSize属性单独设置的,因为CTFontRef和CGFontRef并不像UIFont一样包含点大小。这个例子会告诉你如何将UIFont转换成CGFontRef。

另外,CATextLayer的string属性并不是你想象的NSString类型,而是id类型。这样你既可以用NSString也可以用NSAttributedString来指定文本了(注意,NSAttributedString并不是NSString的子类)。属性化字符串是iOS用来渲染字体风格的机制,它以特定的方式来决定指定范围内的字符串的原始信息,比如字体,颜色,字重,斜体等。

注意事项

  • 若指定了path属性,则values属性将被忽略
  • CABasicAnimation相当于只有两个关键帧的CAKeyframeAnimation
  • values(NSArray *)存放关键帧的多个值,类似于CABasicAnimation的fromValue与toValue值
  • path(CGPathRef)动画的执行路径,可以通过绘图的方式绘制路径
  • keyTimes(NSArray *)每个关键帧的执行时间,类型为NSNumber类型,若不指定,则所有的关键帧平分动画的duration时长
  • timingFunctions(NSArray *)速度控制函数数组
  • calculationMode(NSString *)指定关键帧的动画属性,若指定该值,则keyTimes与timingFunctions属性值将被忽略,默认为:kCAAnimationLinear
  • rotationMode(NSString *)指定旋转模式,默认为nil

CAShapeLayer 根据UIBezierPath绘制的曲线路径渲染出图形, 利用GPU渲染节省内存,提高性能;

富文本

iOS 6中,Apple给UILabel和其他UIKit文本视图添加了直接的属性化字符串的支持,应该说这是一个很方便的特性。不过事实上从iOS3.2开始CATextLayer就已经支持属性化字符串了。这样的话,如果你想要支持更低版本的iOS系统,CATextLayer无疑是你向界面中增加富文本的好办法,而且也不用去跟复杂的Core Text打交道,也省了用UIWebView的麻烦。

#import "DrawingView.h"
#import 
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *labelView;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  textLayer.contentsScale = [UIScreen mainScreen].scale;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //choose some text
  NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing  elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar  leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc  elementum, libero ut porttitor dictum, diam odio congue lacus, vel  fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet  lobortis";

  //create attributed string
  NSMutableAttributedString *string = nil;
  string = [[NSMutableAttributedString alloc] initWithString:text];

  //convert UIFont to a CTFont
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFloat fontSize = font.pointSize;
  CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);

  //set text attributes
  NSDictionary *attribs = @{
    (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  };

  [string setAttributes:attribs range:NSMakeRange(0, [text length])];
  attribs = @{
    (__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
    (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  };
  [string setAttributes:attribs range:NSMakeRange(6, 5)];

  //release the CTFont we created earlier
  CFRelease(fontRef);

  //set layer text
  textLayer.string = string;
}
@end
CAKeyframeAnimation类为对象提供了关键帧动画的功能。你创建一个CAKeyframeAnimation对象使用animationWithKeyPath:指定属性的关键路径,你可以指定要使用关键帧的值来控制时间和动画的行为。我们可以通过CAKeyframeAnimation来实现过山车在轨道上的的动画和云朵的动画:

新葡京8455 81505206576473.jpg

CAGradientLayer 用来绘制颜色渐变的图层, 最后添加到view的layer用来展示;

行距和字距

有必要提一下的是,由于绘制的实现机制不同(Core Text和WebKit),用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不是不尽相同的。

二者的差异程度(由使用的字体和字符决定)总的来说挺小,但是如果你想正确的显示普通便签和CATextLayer就一定要记住这一点。

代码如下:
 CALayer *carLayer = [CALayer layer]; carLayer.frame = CGRectMake(0, 0, 17, 11); UIImage *image = [UIImage imageNamed:@"othercar"]; CGImageRef imageRef = image.CGImage; carLayer.contents = (__bridge id _Nullable); UIBezierPath *bluePath = [UIBezierPath bezierPath]; bluePath.lineCapStyle = kCGLineCapRound; bluePath.lineJoinStyle = kCGLineCapRound; [bluePath moveToPoint:CGPointMake(kScreenWidth2  10, kScreenHeight2 - 7)]; [bluePath addLineToPoint:CGPointMake(kScreenWidth2   10, kScreenHeight2 - 77)]; [bluePath addQuadCurveToPoint:CGPointMake(kScreenWidth2 / 1.8, kScreenHeight2 - 77) controlPoint:CGPointMake(kScreenWidth2 - 120, 193)]; [bluePath addArcWithCenter:CGPointMake(kScreenWidth2 / 1.9, kScreenHeight2 - 140) radius:63 startAngle:0.5*M_PI endAngle:2.5*M_PI clockwise:true]; [bluePath addCurveToPoint:CGPointMake(0, kScreenHeight2 - 107) controlPoint1:CGPointMake(kScreenWidth2 /1.8 - 60, kScreenHeight2 - 67) controlPoint2:CGPointMake(150, kScreenHeight2 /2.3 -7)]; [bluePath addLineToPoint:CGPointMake(-100, kScreenHeight2  7)]; CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];//简单的创建一个带路径的动画效果,比较粗糙 animation.path = bluePath.CGPath; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];//CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。 // 速度控制函数(CAMediaTimingFunction) // 1.kCAMediaTimingFunctionLinear:匀速,给你一个相对静态的感觉 // 2.kCAMediaTimingFunctionEaseIn:动画缓慢进入,然后加速离开 // 3.kCAMediaTimingFunctionEaseOut:动画全速进入,然后减速的到达目的地 // 4.kCAMediaTimingFunctionEaseInEaseOut:动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。 animation.duration = 6;//动画持续时间 animation.beginTime = begintime; animation.repeatCount = MAXFLOAT; animation.autoreverses =false; animation.calculationMode = kCAAnimationCubicPaced;//自动计算 animation.rotationMode = kCAAnimationRotateAuto;//动画角度自动调整 [self.view.layer addSublayer:carLayer]; [carLayer addAnimation:animation forKey:@"carAnimation"];

步骤:

UILabel的替代品

我们已经证实了CATextLayer比UILabel有着更好的性能表现,同时还有额外的布局选项并且在iOS 5上支持富文本。但是与一般的标签比较而言会更加繁琐一些。如果我们真的在需求一个UILabel的可用替代品,最好是能够在Interface Builder上创建我们的标签,而且尽可能地像一般的视图一样正常工作。

我们应该继承UILabel,然后添加一个子图层CATextLayer并重写显示文本的方法。但是仍然会有由UILabel
的-drawRect:方法创建的空寄宿图。而且由于CALayer不支持自动缩放和自动布局,子视图并不是主动跟踪视图边界的大小,所以每次视图大小被更改,我们不得不手动更新子图层的边界。

我们真正想要的是一个用CATextLayer作为宿主图层的UILabel子类,这样就可以随着视图自动调整大小而且也没有冗余的寄宿图啦。

就像我们在第一章『图层树』讨论的一样,每一个UIView都是寄宿在一个CALayer的示例上。这个图层是由视图自动创建和管理的,那我们可以用别的图层类型替代它么?一旦被创建,我们就无法代替这个图层了。但是如果我们继承了UIView,那我们就可以重写 layerClass方法使得在创建的时候能返回一个不同的图层子类。UIView
会在初始化的时候调用 layerClass方法,然后用它的返回类型来创建宿主图层。

#import "LayerLabel.h"
#import 

@implementation LayerLabel
  (Class)layerClass
{
  //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
  return [CATextLayer class];
}

- (CATextLayer *)textLayer
{
  return (CATextLayer *)self.layer;
}

- (void)setUp
{
  //set defaults from UILabel settings
  self.text = self.text;
  self.textColor = self.textColor;
  self.font = self.font;

  //we should really derive these from the UILabel settings too
  //but that's complicated, so for now we'll just hard-code them
  [self textLayer].alignmentMode = kCAAlignmentJustified;

  [self textLayer].wrapped = YES;
  [self.layer display];
}

- (id)initWithFrame:(CGRect)frame
{
  //called when creating label programmatically
  if (self = [super initWithFrame:frame]) {
    [self setUp];
  }
  return self;
}

- (void)awakeFromNib
{
  //called when creating label using Interface Builder
  [self setUp];
}

- (void)setText:(NSString *)text
{
  super.text = text;
  //set layer text
  [self textLayer].string = text;
}

- (void)setTextColor:(UIColor *)textColor
{
  super.textColor = textColor;
  //set layer text color
  [self textLayer].foregroundColor = textColor.CGColor;
}

- (void)setFont:(UIFont *)font
{
  super.font = font;
  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  [self textLayer].font = fontRef;
  [self textLayer].fontSize = font.pointSize;

  CGFontRelease(fontRef);
}
@end

如果你运行代码,你会发现文本并没有像素化,而我们也没有设置contentsScale属性。把CATextLayer作为宿主图层的另一好处就是视图自动设置了contentsScale属性。

在这个简单的例子中,我们只是实现了UILabel的一部分风格和布局属性,不过稍微再改进一下我们就可以创建一个支持UILabel所有功能甚至更多功能的LayerLabel类(你可以在一些线上的开源项目中找到)。

如果你打算支持iOS 6及以上,基于CATextLayer的标签可能就有有些局限性。但是总得来说,如果想在app里面充分利用CALayer子类,用 layerClass来创建基于不同图层的视图是一个简单可复用的方法。

参考文档:CALayer中文翻译

COPY原链接

先创建一个UIView, 在其内部实现圆环的绘制.

6.3 CATransformLayer

如果你用一个普通的layer去承载多个子layer。是无法展现出,3d效果的。用CATransformLayer就可以。

CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (CALayer *)faceWithTransform:(CATransform3D)transform
{
  //create cube face layer
  CALayer *face = [CALayer layer];
  face.frame = CGRectMake(-50, -50, 100, 100);

  //apply a random color
  CGFloat red = (rand() / (double)INT_MAX);
  CGFloat green = (rand() / (double)INT_MAX);
  CGFloat blue = (rand() / (double)INT_MAX);
  face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

  //apply the transform and return
  face.transform = transform;
  return face;
}

- (CALayer *)cubeWithTransform:(CATransform3D)transform
{
  //create cube layer
  CATransformLayer *cube = [CATransformLayer layer];

  //add cube face 1
  CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
  [cube addSublayer:[self faceWithTransform:ct]];

  //add cube face 2
  ct = CATransform3DMakeTranslation(50, 0, 0);
  ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
  [cube addSublayer:[self faceWithTransform:ct]];

  //add cube face 3
  ct = CATransform3DMakeTranslation(0, -50, 0);
  ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
  [cube addSublayer:[self faceWithTransform:ct]];

  //add cube face 4
  ct = CATransform3DMakeTranslation(0, 50, 0);
  ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
  [cube addSublayer:[self faceWithTransform:ct]];

  //add cube face 5
  ct = CATransform3DMakeTranslation(-50, 0, 0);
  ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
  [cube addSublayer:[self faceWithTransform:ct]];

  //add cube face 6
  ct = CATransform3DMakeTranslation(0, 0, -50);
  ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
  [cube addSublayer:[self faceWithTransform:ct]];

  //center the cube layer within the container
  CGSize containerSize = self.containerView.bounds.size;
  cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);

  //apply the transform and return
  cube.transform = transform;
  return cube;
}

- (void)viewDidLoad
{
  [super viewDidLoad];

  //set up the perspective transform
  CATransform3D pt = CATransform3DIdentity;
  pt.m34 = -1.0 / 500.0;
  self.containerView.layer.sublayerTransform = pt;

  //set up the transform for cube 1 and add it
  CATransform3D c1t = CATransform3DIdentity;
  c1t = CATransform3DTranslate(c1t, -100, 0, 0);
  CALayer *cube1 = [self cubeWithTransform:c1t];
  [self.containerView.layer addSublayer:cube1];

  //set up the transform for cube 2 and add it
  CATransform3D c2t = CATransform3DIdentity;
  c2t = CATransform3DTranslate(c2t, 100, 0, 0);
  c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
  c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
  CALayer *cube2 = [self cubeWithTransform:c2t];
  [self.containerView.layer addSublayer:cube2];
}
@end

@property (strong, nonatomic)UIView                *bgview;

6.4 CAGradientLayer

CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。

_bgview = [[UIView alloc]initWithFrame:CGRectMake(50, 50, 250, 250)];

基础渐变

我们将从一个简单的红变蓝的对角线渐变开始.这些渐变色彩放在一个数组中,并赋给colors属性。这个数组成员接受CGColorRef类型的值(并不是从NSObject派生而来),所以我们要用通过bridge转换以确保编译正常。

CAGradientLayer也有startPoint和endPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  //create gradient layer and add it to our container view
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = self.containerView.bounds;
  [self.containerView.layer addSublayer:gradientLayer];

  //set gradient colors
  gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];

  //set gradient start and end points
  gradientLayer.startPoint = CGPointMake(0, 0);
  gradientLayer.endPoint = CGPointMake(1, 1);
}
@end

_bgview.backgroundColor = [UIColor grayColor];

多重渐变

如果你愿意,colors属性可以包含很多颜色,所以创建一个彩虹一样的多重渐变也是很简单的。默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用locations属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。

locations数组并不是强制要求的,但是如果你给它赋值了就一定要确保locations的数组大小和colors数组大小一定要相同,否则你将会得到一个空白的渐变。

- (void)viewDidLoad {
  [super viewDidLoad];

  //create gradient layer and add it to our container view
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = self.containerView.bounds;
  [self.containerView.layer addSublayer:gradientLayer];

  //set gradient colors
  gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];

  //set locations
  gradientLayer.locations = @[@0.0, @0.25, @0.5];

  //set gradient start and end points
  gradientLayer.startPoint = CGPointMake(0, 0);
  gradientLayer.endPoint = CGPointMake(1, 1);
}

[self.view addSubview:_bgview];

6.5 CAReplicatorLayer

CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。看上去演示能够更加解释这些,我们来写个例子吧。

新葡京8455 9

重复图层(Repeating Layers)

我们在屏幕的中间创建了一个小白色方块图层,然后用CAReplicatorLayer生成十个图层组成一个圆圈。instanceCount属性指定了图层需要重复多少次。instanceTransform指定了一个CATransform3D3D变换(这种情况下,下一图层的位移和旋转将会移动到圆圈的下一个点)。

变换是逐步增加的,每个实例都是相对于前一实例布局。这就是为什么这些复制体最终不会出现在同意位置上,

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a replicator layer and add it to our view
    CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
    replicator.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:replicator];

    //configure the replicator
    replicator.instanceCount = 10;

    //apply a transform for each instance
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DTranslate(transform, 0, 200, 0);
    transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
    transform = CATransform3DTranslate(transform, 0, -200, 0);
    replicator.instanceTransform = transform;

    //apply a color shift for each instance
    replicator.instanceBlueOffset = -0.1;
    replicator.instanceGreenOffset = -0.1;

    //create a sublayer and place it inside the replicator
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f);
    layer.backgroundColor = [UIColor whiteColor].CGColor;
    [replicator addSublayer:layer];
}
@end

注意到当图层在重复的时候,他们的颜色也在变化:这是用instanceBlueOffset和instanceGreenOffset属性实现的。通过逐步减少蓝色和绿色通道,我们逐渐将图层颜色转换成了红色。这个复制效果看起来很酷,但是CAReplicatorLayer真正应用到实际程序上的场景比如:一个游戏中导弹的轨迹云,或者粒子爆炸(尽管iOS 5已经引入了CAEmitterLayer,它更适合创建任意的粒子效果)。除此之外,还有一个实际应用是:反射。

Paste_Image.png

反射

使用CAReplicatorLayer并应用一个负比例变换于一个复制图层,你就可以创建指定视图(或整个视图层次)内容的镜像图片,这样就创建了一个实时的『反射』效果。让我们来尝试实现这个创意:指定一个继承于UIView
的ReflectionView,它会自动产生内容的反射效果。

#import "ReflectionView.h"
#import 

@implementation ReflectionView

  (Class)layerClass
{
    return [CAReplicatorLayer class];
}

- (void)setUp
{
    //configure replicator
    CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
    layer.instanceCount = 2;

    //move reflection instance below original and flip vertically
    CATransform3D transform = CATransform3DIdentity;
    CGFloat verticalOffset = self.bounds.size.height   2;
    transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
    transform = CATransform3DScale(transform, 1, -1, 0);
    layer.instanceTransform = transform;

    //reduce alpha of reflection layer
    layer.instanceAlphaOffset = -0.6;
}

- (id)initWithFrame:(CGRect)frame
{
    //this is called when view is created in code
    if ((self = [super initWithFrame:frame])) {
        [self setUp];
    }
    return self;
}

- (void)awakeFromNib
{
    //this is called when view is created from a nib
    [self setUp];
}
@end

实际上用ReflectionView实现这个效果会更简单,我们只需要把ReflectionView的实例放置于Interface Builder,它就会实时生成子视图的反射,而不需要别的代码

2.绘制背景圆环, 并将其添加到view的layer层上.

6.6 CAScrollLayer

//创建背景圆环

6.7 CATiledLayer

CAShapeLayer*trackLayer = [CAShapeLayerlayer];

6.8 CAEmitterLayer

在iOS 5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayer。CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。

CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。

#import "ViewController.h"
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@end


@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    //create particle emitter layer
    CAEmitterLayer *emitter = [CAEmitterLayer layer];
    emitter.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:emitter];

    //configure emitter
    emitter.renderMode = kCAEmitterLayerAdditive;
    emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);

    //create a particle template
    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
    cell.birthRate = 150;
    cell.lifetime = 5.0;
    cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
    cell.alphaSpeed = -0.4;
    cell.velocity = 50;
    cell.velocityRange = 50;
    cell.emissionRange = M_PI * 2.0;

    //add particle template to emitter
    emitter.emitterCells = @[cell];
}
@end

CAEMitterCell的属性基本上可以分为三种:

  • 这种粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。
  • 例子某一属性的变化范围。比如emissionRange属性的值是2π,这意味着例子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形
  • 指定值在时间线上的变化。比如,在示例中,我们将alphaSpeed设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐小时的效果。

CAEmitterLayer的属性它自己控制着整个例子系统的位置和形状。一些属性比如birthRate,lifetime和celocity,这些属性在CAEmitterCell中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个例子系统。其他值得提到的属性有以下这些:

  • preservesDepth,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层
    renderMode,控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为
  • kCAEmitterLayerAdditive,它实现了这样一个效果:合并例子重叠部分的亮度使得看上去更亮。如果我们把它设置为默认的kCAEmitterLayerUnordered,效果就没那么好看了

trackLayer.frame=self.bounds;

6.9 CAEAGLLayer

当iOS要处理高性能图形绘制,必要时就是OpenGL。应该说它应该是最后的杀手锏,至少对于非游戏的应用来说是的。因为相比Core Animation和UIkit框架,它不可思议地复杂。

你需要将GLKit和OpenGLES框架加入到你的项目中,然后就可以实现清单6.14中的代码,里面是设置一个GAEAGLLayer的最少工作,它使用了OpenGL ES 2.0 的绘图上下文,并渲染了一个有色三角

#import "ViewController.h"
#import 
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *glView;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint framebuffer;
@property (nonatomic, assign) GLuint colorRenderbuffer;
@property (nonatomic, assign) GLint framebufferWidth;
@property (nonatomic, assign) GLint framebufferHeight;
@property (nonatomic, strong) GLKBaseEffect *effect;

@end

@implementation ViewController

- (void)setUpBuffers
{
    //set up frame buffer
    glGenFramebuffers(1, &_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);

    //set up color render buffer
    glGenRenderbuffers(1, &_colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
    [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);

    //check success
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
}

- (void)tearDownBuffers
{
    if (_framebuffer) {
        //delete framebuffer
        glDeleteFramebuffers(1, &_framebuffer);
        _framebuffer = 0;
    }

    if (_colorRenderbuffer) {
        //delete color render buffer
        glDeleteRenderbuffers(1, &_colorRenderbuffer);
        _colorRenderbuffer = 0;
    }
}

- (void)drawFrame {
    //bind framebuffer & set viewport
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glViewport(0, 0, _framebufferWidth, _framebufferHeight);

    //bind shader program
    [self.effect prepareToDraw];

    //clear the screen
    glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);

    //set up vertices
    GLfloat vertices[] = {
        -0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
    };

    //set up colors
    GLfloat colors[] = {
        0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
    };

    //draw triangle
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    //present render buffer
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    //set up context
    self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.glContext];

    //set up layer
    self.glLayer = [CAEAGLLayer layer];
    self.glLayer.frame = self.glView.bounds;
    [self.glView.layer addSublayer:self.glLayer];
    self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};

    //set up base effect
    self.effect = [[GLKBaseEffect alloc] init];

    //set up buffers
    [self setUpBuffers];

    //draw frame
    [self drawFrame];
}

- (void)viewDidUnload
{
    [self tearDownBuffers];
    [super viewDidUnload];
}

- (void)dealloc
{
    [self tearDownBuffers];
    [EAGLContext setCurrentContext:nil];
}
@end

在一个真正的OpenGL应用中,我们可能会用NSTimer或CADisplayLink周期性地每秒钟调用-drawRrame方法60次,同时会将几何图形生成和绘制分开以便不会每次都重新生成三角形的顶点(这样也可以让我们绘制其他的一些东西而不是一个三角形而已),不过上面这个例子已经足够演示了绘图原则了。

//清空填充色

6.10 AVPlayerLayer

尽管它不是Core Animation框架的一部分(AV前缀看上去像),AVPlayerLayer是有别的框架(AVFoundation)提供的,它和Core Animation紧密地结合在一起,提供了一个CALayer子类来显示自定义的内容类型。

AVPlayerLayer是用来在iOS上播放视频的。他是高级接口例如MPMoivePlayer的底层实现,提供了显示视频的底层控制。AVPlayerLayer的使用相当简单:你可以用 playerLayerWithPlayer:方法创建一个已经绑定了视频播放器的图层,或者你可以先创建一个图层,然后用player属性绑定一个AVPlayer实例。

在我们开始之前,我们需要添加AVFoundation到我们的项目中。

#import "ViewController.h"
#import 
#import 

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView; @end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //get video URL
    NSURL *URL = [[NSBundle mainBundle] URLForResource:@"Ship" withExtension:@"mp4"];

    //create player and player layer
    AVPlayer *player = [AVPlayer playerWithURL:URL];
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];

    //set player layer frame and attach it to our view
    playerLayer.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:playerLayer];

    //play the video
    [player play];
}
@end

当然,因为AVPlayerLayer是CALayer的子类,它继承了父类的所有特性。我们并不会受限于要在一个矩形中播放视频;清单6.16演示了在3D,圆角,有色边框,蒙板,阴影等效果

- (void)viewDidLoad
{
    ...
    //set player layer frame and attach it to our view
    playerLayer.frame = self.containerView.bounds;
    [self.containerView.layer addSublayer:playerLayer];

    //transform layer
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0 / 500.0;
    transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
    playerLayer.transform = transform;

    //add rounded corners and border
    playerLayer.masksToBounds = YES;
    playerLayer.cornerRadius = 20.0;
    playerLayer.borderColor = [UIColor redColor].CGColor;
    playerLayer.borderWidth = 5.0;

    //play the video
    [player play];
}

trackLayer.fillColor= [UIColorclearColor].CGColor;

//设置画笔颜色 即圆环背景色

trackLayer.strokeColor=  [UIColorcolorWithRed:170/255.0green:210/255.0blue:254/255.0alpha:1].CGColor;trackLayer.lineWidth=20;

//设置画笔路径

UIBezierPath*path = [UIBezierPathbezierPathWithArcCenter:CGPointMake(self.frame.size.width/2.0,self.frame.size.height/2.0) radius:self.frame.size.width/2.0-10startAngle:- M_PI_2 endAngle:-M_PI_2 M_PI *2clockwise:YES];

//path 决定layer将被渲染成何种形状

trackLayer.path= path.CGPath;

[self.layeraddSublayer:trackLayer];

新葡京8455 10

Paste_Image.png

  1. 使用CAGradientLayer 绘制渐变的背景色

由于CAShapeLayer不能顺着弧线进行渐变只能指定两个点之间进行渐变, 所以四种颜色的渐变只能是将view分成四个小的部分, 分别实现两种颜色间的渐变. 最后在组合在一起, 形成四种颜色的渐变.

CGFloat width = _bgview.frame.size.width;

CGFloat height = _bgview.frame.size.height;

CALayer *gradientLayer = [CALayer layer];

gradientLayer.frame = self.bgview.bounds;

CAGradientLayer *gradientLayer1 = [CAGradientLayer layer];

gradientLayer1.frame = CGRectMake(width/2.0, 0, width/2.0,  height/2.0);

gradientLayer1.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor orangeColor].CGColor];

//startPoint和endPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}

//startPoint和pointEnd 分别指定颜色变换的起始位置和结束位置.

//当开始和结束的点的x值相同时, 颜色渐变的方向为纵向变化

//当开始和结束的点的y值相同时, 颜色渐变的方向为横向变化

//其余的 颜色沿着对角线方向变化

gradientLayer1.startPoint = CGPointMake(0.2, 0);

gradientLayer1.endPoint = CGPointMake(0.8, 1);

[gradientLayer addSublayer:gradientLayer1];

CAGradientLayer *gradientLayer2 = [CAGradientLayer layer];

gradientLayer2.frame = CGRectMake(width/2.0, width/2.0, width/2.0,  height/2.0);

gradientLayer2.colors = @[(__bridge id)[UIColor orangeColor].CGColor, (__bridge id)[UIColor yellowColor].CGColor];

[gradientLayer2 setLocations:@[@0.3, @0.8,@1]];

gradientLayer2.startPoint = CGPointMake(0, 0);

gradientLayer2.endPoint = CGPointMake(0, 1);

[gradientLayer addSublayer:gradientLayer2];

CAGradientLayer *gradientLayer3 = [CAGradientLayer layer];

gradientLayer3.frame = CGRectMake(0, width/2.0, width/2.0,  height/2.0);

gradientLayer3.colors = @[(__bridge id)[UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];

gradientLayer3.startPoint = CGPointMake(0.5, 1);

gradientLayer3.endPoint = CGPointMake(0.5, 0);

[gradientLayer addSublayer:gradientLayer3];

CAGradientLayer *gradientLayer4 = [CAGradientLayer layer];

gradientLayer4.frame = CGRectMake(0, 0, width/2.0,  height/2.0);

gradientLayer4.colors = @[(__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];

gradientLayer4.startPoint = CGPointMake(0.5, 1);

gradientLayer4.endPoint = CGPointMake(0.5, 0);

[gradientLayer addSublayer:gradientLayer4];

[self.bgview.layer addSublayer:gradientLayer];

新葡京8455 11

  1. 将颜色图层切成圆环

@property (nonatomic, strong) CAShapeLayer *progressLayer;

_progressLayer = [CAShapeLayer layer];

_progressLayer.frame = self.bounds;

_progressLayer.fillColor = [UIColor clearColor].CGColor;

_progressLayer.strokeColor  = [UIColor colorWithRed:170/255.0 green:210/255.0 blue:254/255.0 alpha:1].CGColor;

_progressLayer.lineWidth = 20;

_progressLayer.path = path.CGPath;

gradientLayer.mask = _progressLayer;

新葡京8455 12

Paste_Image.png

  1. 利用locations 属性调整颜色空间

根据上面的运行结果可以看出底部的颜色交接的地方有颜色的断层. 主要是第三部分底部的黄色区域较小导致的. 可以利用locations数组进行微调.

默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用locations属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。

[gradientLayer2 setLocations:@[@0.3, @0.8]];

新葡京8455 13

Paste_Image.png

本文由新葡京8455发布,转载请注明来源

关键词: