iOS下的图形绘制
图形绘制
iOS系统本身提供了两套绘图的框架,即UIBezierPath 和 Core Graphics。而前者所属UIKit,其实是对Core Graphics框架关于path的进一步封装,所以使用起来比较简单。但是毕竟Core Graphics更接近底层,所以它更加强大。
UIBezierPath
UIKit中的UIBezierPath是Core Graphics框架关于path的一个封装。可以创建基于矢量的路径,例如椭圆或者矩形,或者有多个直线和曲线段组成的形状。我们可以用moveToPoint: 和 addLineToPoint:方法去构建。moveToPoint:设置我们想要创建形状的起点。从这点开始,我们可以用方法addLineToPoint:去创建一个形状的线段。我们可以连续的创建line,每一个line的起点都是先前的终点,终点就是指定的点。closePath可以在最后一个点和第一个点之间画一条线段。
- (void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor colorWithRed:0 green:0.7 blue:0 alpha:1];
[color set];
UIBezierPath* aPath = [UIBezierPath bezierPath];
aPath.lineWidth = 5.0;
aPath.lineCapStyle = kCGLineCapRound;
aPath.lineJoinStyle = kCGLineCapRound;
// 起点
[aPath moveToPoint:CGPointMake(100.0, 0.0)];
// 绘制线条
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];//第五条线通过调用closePath方法得到的
//根据坐标点连线
[aPath stroke];
[aPath fill];
}
UIBezierPath+CAShapeLayer
CAShapeLayer 继承自 CALayer ,因此,可使用 CALayer 的所有属性。但是, CAShapeLayer 需要和贝塞尔曲线配合使用才有意义。
查看官网说明:
/* The shape layer draws a cubic Bezier spline in its coordinate space.
- The spline is described using a CGPath object and may have both fill
- and stroke components (in which case the stroke is composited over
- the fill). The shape as a whole is composited between the layer’s
- contents and its first sublayer.
*/
这里是说 CAShapeLayer 是在其坐标系统内绘制贝塞尔曲线的。因此,使用 CAShapeLayer 需要与 UIBezierPath 一起使用。它有一个 path 属性,而 UIBezierPath 就是对 CGPathRef 类型的封装,因此这两者要配合起来用。
CAShapeLayer与drawRect的关系
- drawRect :属于 CoreGraphics 框架,占用 CPU ,性能消耗大,不建议重写
- CAShapeLayer :属于 CoreAnimation 框架,通过 GPU 来渲染图形,节省性能。动画渲染直接提交给手机 GPU ,不消耗内存
这两者各有各的用途,而不是说有了 CAShapeLayer 就不需要 drawRect。
温馨提示:drawRect只是一个方法而已,是 UIView 的方法,重写此方法可以完成我们的绘制图形功能。
CAShapeLayer与UIBezierPath的关系
- CAShapeLayer中shape代表形状的意思,所以需要形状才能生效
- 贝塞尔曲线可以创建基于矢量的路径,而UIBezierPath类是对CGPathRef的封装
- 贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染。路径会闭环,所以绘制出了Shape
- 用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线
- (void)viewDidLoad {
[super viewDidLoad];
_triangleView = [[UIView alloc] initWithFrame:CGRectMake(screenWidth / 4, 200, screenWidth / 2, screenHeight / 2)];
_triangleView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:_triangleView];
_triangleView.layer.mask = [self createMaskLayerWithView];
}
- (CALayer *)createMaskLayerWithView
{
CGFloat viewWidth = CGRectGetWidth(_triangleView.frame);
CGFloat viewHeight = CGRectGetHeight(_triangleView.frame);
CGFloat rightSpace = 10.;
CGFloat topSpace = 15.;
// 起点
CGPoint point1 = CGPointMake(0, 0);
// 绘制线条
CGPoint point2 = CGPointMake(viewWidth-rightSpace, 0);
CGPoint point3 = CGPointMake(viewWidth-rightSpace, topSpace);
CGPoint point4 = CGPointMake(viewWidth, topSpace);
CGPoint point5 = CGPointMake(viewWidth-rightSpace, topSpace+10.);
CGPoint point6 = CGPointMake(viewWidth-rightSpace, viewHeight);
CGPoint point7 = CGPointMake(0, viewHeight);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point1];
[path addLineToPoint:point2];
[path addLineToPoint:point3];
[path addLineToPoint:point4];
[path addLineToPoint:point5];
[path addLineToPoint:point6];
[path addLineToPoint:point7];
[path closePath];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
return layer;
}
CoreGraphics
这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit,Core Graphics是高度集成于UIView和其他UIKit部分,Core Graphics数据结构和函数可以通过前缀CG来识别。
步骤:
- 1.先在drawRect方法中获得上下文context;
- 2.绘制图形(线,图形,图片等);
- 3.设置一些修饰属性;
- 4.渲染到上下文,完成绘图。
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(c, 0.0, 0.0, 0.0, 1.0); // black
CGContextSetLineWidth(c, 1);
CGMutablePathRef bubblePath = CGPathCreateMutable();
// 这里的点是三角形的尖尖
CGPathMoveToPoint(bubblePath, NULL, self.frame.origin.x / 2, self.frame.origin.y);
// 其中的一条边的终点
CGPathAddLineToPoint(bubblePath, NULL, 0, self.frame.size.height / 3);
// 另一条边的终点
CGPathAddLineToPoint(bubblePath, NULL, self.frame.size.width, self.frame.size.height / 3);
CGPathCloseSubpath(bubblePath);
CGContextSaveGState(c);
CGContextAddPath(c, bubblePath);
CGContextClip(c);
CGContextSetFillColorWithColor(c, [[UIColor blueColor] CGColor]);
CGContextFillRect(c, self.bounds);
CGPathRelease(bubblePath);
}
注意事项:
- 1.绘图需要 CGContextRef,CGContextRef即图形上下文。可以这么理解,我们绘图是需要一个载体或者说输出目标,它用来显示绘图信息,并且决定绘制的东西输出到哪个地方。可以形象的比喻context就像一个“画板”,我们得把图形绘制到这个画板上。所以,绘图必须要先有context;
- 2.并不是说一提到绘图,就一定得重写drawRect方法,只是因为通常情况下我们一般采用在drawRect方法里获取context这种方式。
- drawRect方法什么时候触发?当view第一次显示到屏幕上时;当调用view的setNeedsDisplay或者setNeedsDisplayInRect:方法时。
CGContextAddArcToPoint && CGPathAddArcToPoint
CGContextAddArcToPoint与CGPathAddArcToPoint这两函数是根据两切线及角度来画弧度,设置弧度CGFloat radius。
画一个四个角都是圆角的矩形,
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self drawArcToPoint2];
}
- (void)drawArcToPoint2
{
//创建CGContextRef
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef gc = UIGraphicsGetCurrentContext();
//===绘画逻辑 ===
//创建用于转移坐标的Transform,如许我们不消遵守实际显示做坐标策画
CGAffineTransform transform = CGAffineTransformMakeTranslation(200,200);
//创建CGMutablePathRef
CGMutablePathRef path = CGPathCreateMutable();
//半径为10
CGFloat radius = 10;
//初始点为(10, 0),起点要从10开始,否则,在左上角会出现线条
CGPathMoveToPoint(path, &transform, 10, 0);
//右上角和右下角两个点,画出半个圆角
CGPathAddArcToPoint(path, &transform,200, 0, 200, 200, radius);
//右下角,画出别的半个圆角
CGPathAddArcToPoint(path, &transform,200, 200, 0, 200, radius);
//左下角
CGPathAddArcToPoint(path, &transform,0, 200, 0,0, radius);
//左上角
CGPathAddArcToPoint(path, &transform,0, 0, 200, 0, radius);
//将CGMutablePathRef添加到当前Context内
CGContextAddPath(gc, path);
[[UIColor grayColor] setFill];
[[UIColor blueColor] setStroke];
CGContextSetLineWidth(gc,2);
//履行绘画
CGContextDrawPath(gc,kCGPathFillStroke);
//从Context中获取图像,并显示在界面上
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:imgView];
}
CAShapedLayer处理
理论上我们可以构造出任意想要的形状,但是有些形状如果你不熟悉几何知识的话是构造不出正确path的,从代码上我们可以看到我们可以通过设置CALayer的contents属性来设置显示的内容,那我们是不是可以通过设置CAShapedLayer的contents来设maskLayer呢?答案是肯定的,代码如下:
- (void)setup4
{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [UIColor blackColor].CGColor;
maskLayer.strokeColor = [UIColor clearColor].CGColor;
maskLayer.frame = self.bounds;
maskLayer.contentsCenter = CGRectMake(0.5, 0.5, 0.1, 0.1);
maskLayer.contentsScale = [UIScreen mainScreen].scale; //非常关键设置自动拉伸的效果且不变形
maskLayer.contents = (id)[UIImage imageNamed:@"gray_bubble_right@2x.png"].CGImage;
CALayer *contentLayer = [CALayer layer];
contentLayer.mask = _maskLayer;
contentLayer.frame = self.bounds;
[self.layer addSublayer:_contentLayer];
}
遗留问题
使用CGPathAddArcToPoint绘制带有箭头的弹窗菜单,如图,
在绘制四个边的圆角时,无法对应到特定的四个角的位置,导致绘制不出该效果的图片。
demo
Github :https://github.com/XibHe/DrawTriangle
参考资料
- 绘图
- iOS CAShapeLayer精讲
- iOS绘图—— UIBezierPath 和 Core Graphics
- CGPathAddArc vs CGPathAddArcToPoint
- CGContextAddArcToPoint与CGPathAddArcToPoint
- iOS 不规则的ImageView
- CGPathAddArc vs CGPathAddArcToPoint
–EOF–
若无特别说明,本站文章均为原创,转载请保留链接,谢谢