序言

先上本文讲述的demo效果图

图片 1

这几天博主在看kitten yang的A GUIDE TO IOS
ANIMATION,作者对动画的应用令我感触很深(同为同龄人实在觉得惭愧),于是决定重新学习三次layer。
coreAnimation作为iOS最重点的框架之一,CALayer的关键毋庸置疑,本文将从上图的demo讲起,我会分成常规用法跟自己思想实现的用法来兑现,以此来尤其梦寐不忘的上学layer。
ps:本文不包括CALayer的性质讲解以及利用。如有需要,请自行百度学习

进度条

常规做法

如上图所示,进度条并不是单独的线性增长,在50%在此之前,每一遍进度增添,进度条就会在y轴上边偏移一段距离,直到增长到一半进度的时候偏移地方达到顶点,然后趁机速度继续加码,y轴的舞狮越来越小,直到变回一条直线。
从贯彻角度而言,使用CAShapeLayer然后在每回进度改变的时候更新其path值就可以实现。如若运用CAShapeLayer的章程,大家需要创设两个实例对象,一个身处下边作为进度条背景,另一个在地方随着速度改变而改变。图示如下:

图片 2

老是进度暴发改变的时候,我们都要依照最近进度统计出进度坐标地方,然后更新五个图层的path,代码如下:

- (void)updatePath
{
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint: CGPointMake(25, 150)];
    [path addLineToPoint: CGPointMake((CGRectGetWidth([UIScreen mainScreen].bounds) - 50) * _progress + 25, 150 + (25.f * (1 - fabs(_progress - 0.5) * 2)))];
    [path addLineToPoint: CGPointMake(CGRectGetWidth([UIScreen mainScreen].bounds) - 25, 150)];
    self.background.path = path.CGPath;
    self.top.path = path.CGPath;
    self.top.strokeEnd = _progress;
}

实在,使用这种形式贯彻速度效果的时候,进度会比平昔在脚下上下文绘制的响应上要慢上几帧,即是我们肉眼能够看到这种延时更新的效能,是不便民用户体验的。其次,我们需要额外创造一个背景图层,在内存上有了额外的资费。

自定义layer

这小节大家要透过自定义CALayer的子类来实现地点的进度条效果,我们需要对外开放progress属性。每一趟那个值暴发变动的时候大家要调用[self setNeedsDisplay]来再度绘制进度条

@property(nonatomic, assign) CGFloat progress;

重写setter方法,检测进度值范围以及重新绘制进度条

  • (void)setProgress: (CGFloat)progress
    {
    _progress = MIN(1.f, MAX(0.f, progress));
    [self setNeedsDisplay];
    }
    再度记念一下进度条,我们得以把进度条分成两条线,分别是肉色的已形成进度条和藏粉红色的进度条。依据进度条的不同,分为<0.5,
    =0.5, >0.5二种意况:

图片 3

从上图可知,在进度达到一半的时候,我们的进度条在Y轴上的偏移量达到最大值。因而,大家应当定义一个最大偏移值MAX_OFFSET。

#define MAX_OFFSET 25.f

一边,当前进度条的y轴偏移量是依据进度按比例举办偏移的。在大家转移进度_progress的时候,重新绘制进度条。下面是青色进度条的绘图

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs((_progress –
    0.5f) * 2));

      CGMutablePathRef mPath = CGPathCreateMutable();
      CGPathMoveToPoint(mPath, NULL, _origin.x, _origin.y);
      CGPathAddLineToPoint(mPath, NULL, offsetX, offsetY);
    
      CGContextAddPath(ctx, mPath);
      CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
      CGContextSetLineWidth(ctx, 5.f);
      CGContextSetLineCap(ctx, kCGLineCapRound);
      CGContextStrokePath(ctx);
    
      CGPathRelease(mPath);
    

    }

ps:
这边存在一个很重大的问题,自定义的layer必须加在我们自定义的view下面,才能兑现drawInContext:方法举办持续的重绘。关于coreGraphics相关方法的更多使用,请参考这篇著作

第二局部的黑色线条基于当前偏移的坐标为起源举办绘图,在那里有五个小陷阱:

  • 不在行的开发者很容易直接把绘制黄色线条的代码放在上边这段代码的前面。这样会招致绿色线条在紫色线条后边绘制而将红色线条遮住了一有的使得紫色线条端末非圆形
  • 没有对_progress的值举办判定。当_progress为0时,下边的代码也会在线条右边生成一个绿色小圆点,这是不标准的。

因而,我们在规定好当前速度对应的晃动坐标时,应该一直绘制紫色线条,再绘制藏褐色进度条。在绘制红色线条前应当对_progress举行两回判断

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs((_progress –
    0.5f) * 2));

      CGMutablePathRef mPath = CGPathCreateMutable();
      CGPathMoveToPoint(mPath, NULL, offsetX, offsetY);
      CGPathAddLineToPoint(mPath, NULL, _origin.x + MAX_LENGTH, _origin.y);
    
      CGContextAddPath(ctx, mPath);
      CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
      CGContextSetLineWidth(ctx, 5.f);
      CGContextSetLineCap(ctx, kCGLineCapRound);
      CGContextStrokePath(ctx);
      CGPathRelease(mPath);
    
      if (_progress != 0.f) {
          mPath = CGPathCreateMutable();
          CGPathMoveToPoint(mPath, NULL, _origin.x, _origin.y);
          CGPathAddLineToPoint(mPath, NULL, offsetX, offsetY);
    
          CGContextAddPath(ctx, mPath);
          CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
          CGContextSetLineWidth(ctx, 5.f);
          CGContextSetLineCap(ctx, kCGLineCapRound);
          CGContextStrokePath(ctx);
          CGPathRelease(mPath);
      }
    

    }

此时在controller里面加上一个UISlider拖拉来支配你的进度条进度,看看是不是想要的听从完成了。

扩展

下边我们在实现绘制的时候,对填充色彩颜色是写死的,这样不便宜代码扩大。回顾CAShapeLayer,在后续CALayer的底蕴上添加了fillColor、strokeColor等看似属性,我们得以因此充裕类似的成员属性来完成封装,这里我们需要为进度条添加多少个特性,分别表示进度条颜色跟背景颜色

@property(nonatomic, assign) CGColorRef backgroundColor;
@property(nonatomic, assign) CGColorRef strokeColor;

大家在安装颜色的时候从来传入color.CGColor就可以做到赋值了,我们把地方的装置颜色代码分别改成下边所示后再度运行

CGContextSetStrokeColorWithColor(ctx, _backgroundColor);
CGContextSetStrokeColorWithColor(ctx, _strokeColor);

一部分朋友们会发现一个坑爹的事体,崩溃了,出现了EXC_BAD_ACCESS不当——假如您利用系统提供的[UIColor xxxColor].CGColor,那么这里不会出问题。
那是因为我们扩大的四个特性为assign类型,在我们应用这么些color的时候,它早已被保释了。由这里我们可以见到两件工作:

  • CAShapeLayer会对非对象且属于coreGraphics的性质举办桥接或者引用操作
  • [UIColor
    xxxColor]主意重返的对象应当是大局或者静态对象。为了节约内存消耗,应该是利用懒加载模式。有必不可少的场地下,可以不调用那些主意来贯彻优化内存的机能

之所以,我们应该重写那三个特性的setter方法来兑现引用(欢迎来到MRC)

  • (void)setStrokeColor: (CGColorRef)strokeColor
    {
    CGColorRelease(_strokeColor);
    _strokeColor = strokeColor;
    CGColorRetain(_strokeColor);
    [self setNeedsDisplay];
    }
    除开,CAShapeLayer还有一个幽默的性能strokeEnd,那么些特性决定了上上下下图层有微微有些需要被渲染的。想查看那些特性的看官们可以在最起首的健康代码中为layer设置那一个特性,然后你会意识此时不管我们的progress设置为多少,进度条的藏黄色部分总是一样strokeEnd。效果如下图所示

图片 4

可以看出,基于strokeEnd举办绘图的时候,界面的绘图难度进一步复杂了。可是我们一样可以把这一个拆分,分为二种境况
1、strokeEnd>progress
那多少个情状对应图中上边四个图,当然,在progress=1跟progress=0的场所是千篇一律的。可以看到,当progress不为零的时候,进度条分为三有些:

  • 偏移点右边的青色线条
  • 动手多出的肉色线条
  • 最终的黄色线条

交接点的y坐标应当是由strokeEnd超出progress的百分比部分除以当前右侧总长度占线条总长度的比例,如下图所示

图片 5

所以我们需要判定六个坐标点,其中偏移点依据上边代码一样基于progress得出,统计背景象和进度颜色交接点的代码如下:

CGFloat contactX = _origin.x + MAX_LENGTH * _strokeEnd;
CGFloat contactY = _origin.y + (offsetY - _origin.y) * ((1 - (_strokeEnd - _progress) / (1 - _progress)));

2、strokeEnd<=progress
此时就对应下面的两张图了,同样的,我们得以把进度条拆分成三局部:

  • 最左侧的肉色进度条
  • 处于进度条和偏移点中间的背景颜色条
  • 右侧的背景颜色条

按部就班下边的图解形式展开解析,相当于把左侧的职位音信放到了左手,我们得以随心所欲的汲取颜色交接点坐标的推测情势

CGFloat contactX = _origin.x + MAX_LENGTH * _strokeEnd;
CGFloat contactY = (offsetY - _origin.y) * (_progress == 0 ?: _strokeEnd / _progress) + _origin.y;

有了地点的解析统计,drawInContext的代码如下

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs(_progress –
    0.5f) * 2);
    CGFloat contactX = 25.f + MAX_LENGTH * _strokeEnd;
    CGFloat contactY = _origin.y + _maxOffset * (1 – fabs(_strokeEnd –
    0.5f) * 2);

      CGRect textRect = CGRectOffset(_textRect, MAX_LENGTH * _progress, _maxOffset * (1 - fabs(_progress - 0.5f) * 2));
      if (_report) {
          _report((NSUInteger)(_progress * 100), textRect, _strokeColor);
      }
      CGMutablePathRef linePath = CGPathCreateMutable();
    
      //绘制背景线条
      if (_strokeEnd > _progress) {
          CGFloat scale =  _progress == 0 ?: (1 - (_strokeEnd - _progress) / (1 - _progress));
          contactY = _origin.y + (offsetY - _origin.y) * scale;
          CGPathMoveToPoint(linePath, NULL, contactX, contactY);
      } else {
          CGFloat scale = _progress == 0 ?: _strokeEnd / _progress;
          contactY = (offsetY - _origin.y) * scale + _origin.y;
          CGPathMoveToPoint(linePath, NULL, contactX, contactY);
          CGPathAddLineToPoint(linePath, NULL, offsetX, offsetY);
      }
      CGPathAddLineToPoint(linePath, NULL, _origin.x + MAX_LENGTH, _origin.y);
      [self setPath: linePath onContext: ctx color: [UIColor colorWithRed: 204/255.f green: 204/255.f blue: 204/255.f alpha: 1.f].CGColor];
    
      CGPathRelease(linePath);
      linePath = CGPathCreateMutable();
    
      //绘制进度线条
      if (_progress != 0.f) {
          CGPathMoveToPoint(linePath, NULL, _origin.x, _origin.y);
          if (_strokeEnd > _progress) { CGPathAddLineToPoint(linePath, NULL, offsetX, offsetY); }
              CGPathAddLineToPoint(linePath, NULL, contactX, contactY);
          } else {
              if (_strokeEnd != 1.f && _strokeEnd != 0.f) {
                  CGPathMoveToPoint(linePath, NULL, _origin.x, _origin.y);
              CGPathAddLineToPoint(linePath, NULL, contactX, contactY);
          }
      }
      [self setPath: linePath onContext: ctx color: [UIColor colorWithRed: 66/255.f green: 1.f blue: 66/255.f alpha: 1.f].CGColor];
      CGPathRelease(linePath);
    

    }

俺们把添加CGPathRef以及安装线条颜色、大小等参数的代码封装成setPath: onContext: color措施,以此来压缩代码量。
coreAnimation以及coreGraphics用作最主题的框架之一,有很多值得我们去追究的特性,这么些特征是怎么落实的对我们的话是一个迷,不过我们得以品味去追究那个特色。
文集:iOS开发

转载注解链接:CALayer的探索应用

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图