找回密码
 会员注册
查看: 17|回复: 0

贝塞尔曲线在iOS端的绘图实践

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-10-10 21:16:37 | 显示全部楼层 |阅读模式
贝塞尔曲线在iOS端的绘图实践 贝塞尔曲线在iOS端的绘图实践 孙齐@贝壳找房 贝壳产品技术 贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容 2020年09月04日 20:11 1 前言在这个大数据的时代,很多信息只有通过图形才能更好的展示给用户。例如:房屋的历史价格、基金股票的历史增长、数据占比分析图等。如何做图形?需要用到什么知识?本文将从 建模、显示 两方面来展开介绍。2 建模建模是一切图形的基础,其他内容的前提,要用代码展示一个图形,首先要有它的几何模型表达。目前在客户端二维图形建模上,Bézier curve(贝塞尔曲线)可以称为 经典 和 主流 并重的数学曲线。对于贝塞尔曲线来说,最重要的是 起始点、终止点(也称锚点)、控制点。控制点决定了一条路径的弯曲轨迹,根据控制点的个数,贝塞尔曲线被分为:一阶贝塞尔曲线(0个控制点)、二阶贝塞尔曲线(1个控制点)、三阶贝塞尔曲线(2个控制点)、N阶贝塞尔曲线(n - 1个控制点)。2.1 贝塞尔曲线原理以二阶贝塞尔曲线为例 解释说明:起始点:P0 ; 控制点:P1 ; 终止点:P2连接P0P1线 和 P1P2线。在P0P1线上找到点A,在P1P2线上找到点B,使得 P0A/AP1 = P1B/BP2连接AB,在AB上找到点X,X点满足:AX/XB = P0A/AP1 = P1B/BP2找出所有满足公式:AX/XB = P0A/AP1 = P1B/BP2 的X点。(从P0 到 P2的红色曲线点为所有X点的连线)这条由所有X点组成的连线 即为 贝塞尔曲线。二阶贝塞尔曲线 起始点:P0 ; 控制点:P1 ; 终止点:P2三阶贝塞尔曲线 起始点:P0 ; 控制点:P1、P2; 终止点:P3四阶贝塞尔曲线 起始点:P0 ; 控制点:P1、P2、P3 ; 终止点:P42.2 UIBezierPath类系统给我们提供了一个叫做UIBezierPath类,用它可以画简单的圆形,椭圆,矩形,圆角矩形,也可以通过添加点去生成任意的图形,还可以简单的创建一条二阶贝塞尔曲线和三阶贝塞尔曲线。我们来了解一下它的常用方法:2.2.1 初始化方法//创建UIBezierPath对象+(instancetype)bezierPath;//创建在rect内的矩形+(instancetype)bezierPathWithRectCGRect)rect;//设定特定的角为圆角的矩形,corners:指定的角为圆角,其他角不变,cornerRadii:圆角的大小+(instancetype)bezierPathWithRoundedRectCGRect)rectbyRoundingCornersUIRectCorner)cornerscornerRadiiCGSize)cornerRadii;//创建圆弧+(instancetype)bezierPathWithArcCenterCGPoint)centerradiusCGFloat)radiusstartAngleCGFloat)startAngleendAngleCGFloat)endAngleclockwiseBOOL)clockwise;//通过已有路径创建路径+(instancetype)bezierPathWithCGPathCGPathRef)CGPath;//创建三次贝塞尔曲线 endPoint:终点 controlPoint1:控制点1 controlPoint2:控制点2-(void)addCurveToPoint:(CGPoint)endPointcontrolPoint1:(CGPoint)controlPoint1controlPoint2:(CGPoint)controlPoint2;-//创建二次贝塞尔曲线 endPoint:终点 controlPoint:控制点-(void)addQuadCurveToPoint:(CGPoint)endPointcontrolPoint:(CGPoint)controlPoint;2.2.2 使用方法//移动到某一点-(void)moveToPoint:(CGPoint)point;//绘制一条线-(void)addLineToPoint:(CGPoint)point;//闭合路径,即在终点和起点连一根线-(void)closePath;//清空路径-(void)removeAllPoints;//填充-(void)fill;//描边,路径创建需要描边才能显示出来-(void)stroke;2.2.3 常用属性//将UIBezierPath类转换成CGPath,类似于UIColor的CGColor@property(nonatomic)CGPathRefCGPath;//path线的宽度@property(nonatomic)CGFloatlineWidth;//path端点样式@property(nonatomic)CGLineCaplineCapStyle;//拐角样式@property(nonatomic)CGLineJoinlineJoinStyle;2.2.4举个栗子先看效果:代码如下:-(void)drawRect:(CGRect)rect{[[UIColorredColor]set];//右边第一个图UIBezierPath*maskPath=[UIBezierPathbezierPathWithRoundedRect:CGRectMake(50,50,100,100)byRoundingCorners:UIRectCornerTopLeftcornerRadii:CGSizeMake(30,30)];maskPath.lineWidth=20.f;maskPath.lineJoinStyle=kCGLineJoinBevel;[maskPathstroke];//中间第二个图UIBezierPath*maskFillPath=[UIBezierPathbezierPathWithRoundedRect:CGRectMake(200,50,100,100)byRoundingCorners:UIRectCornerTopLeftcornerRadii:CGSizeMake(30,30)];maskFillPath.lineWidth=20.f;maskFillPath.lineJoinStyle=kCGLineJoinBevel;[maskFillPathfill];[maskFillPathstroke];//右边第三个图UIBezierPath*maskLinePath=[UIBezierPathbezierPath];maskLinePath.lineWidth=20.f;maskLinePath.lineCapStyle=kCGLineCapRound;[maskLinePathmoveToPoint:CGPointMake(250.0,50)];[maskLinePathaddLineToPoint:CGPointMake(300.0,100.0)];[maskLinePathstroke];}上图中:1)图一和图二 唯一的不同是[maskFillPath fill]方法,fill方法要在封闭的曲线调用。2)图一和图二 为设定特定的角为圆角的矩形,corners为UIRectCornerTopLeft左上角,cornerRadii圆角大小为30,绿色的箭头 表示的设定的这个角。corners为下面五种类型typedefNS_OPTIONS(NSUInteger,UIRectCorner){UIRectCornerTopLeft=1<<0,//左上角UIRectCornerTopRight=1<<1,//右上角UIRectCornerBottomLeft=1<<2,//左下角UIRectCornerBottomRight=1<<3,//右下角UIRectCornerAllCorners=~0UL//全部};3)图一和图二 黄色的箭头 设置的属性 拐角样式:lineJoinStyle kCGLineJoinBevel(缺角)lineJoinStyle为下面三种类型typedefCF_ENUM(int32_t,CGLineJoin){kCGLineJoinMiter,//尖角kCGLineJoinRound,//圆角kCGLineJoinBevel//缺角};4)图三 白色的箭头 设置的属性 path端点样式:lineCapStyle kCGLineCapRound(圆形端点)lineCapStyle为下面三种类型typedefCF_ENUM(int32_t,CGLineCap){kCGLineCapButt,//无端点kCGLineCapRound,//圆形端点kCGLineCapSquare//方形端点};有兴趣的 可以试试别的方法属性~2.3波浪曲线实现如何实现 N阶 波浪式曲线?如何找到 N-1 个对应的控制点?有两个方法,下图为同数据,方案一 和 方案二 分别所得曲线图。方案一 为左边(三阶贝塞尔)图 其中 第二条的红点 为数据的位置方案二 为右边(CatmullRom)图 其中 第二条的红点 为数据的位置方案一:根据 创建三次贝塞尔曲线 方法 实现波浪曲线控制点的选取方案不唯一,以下为我选择控制点的方案:控制点P1:CGPointMake((PrePonit.x+NowPoint.x)/2, PrePonit.y)控制点P2:CGPointMake((PrePonit.x+NowPoint.x)/2, NowPoint.y)可以根据前一个点PrePonit 和 现在的点NowPoint 进行计算 控制点。主要代码如下:UIBezierPath*path=[UIBezierPathbezierPath];[pathmoveToPoint:[selfpointAtIndex:0]];NSIntegercount=self.points.count;CGPointPrePonit;for(NSIntegeri=0;itype;CGPoint*points=element->points;if(type!=kCGPathElementCloseSubpath){[bezierPointsaddObject:VALUE(0)];if((type!=kCGPathElementAddLineToPoint)&(type!=kCGPathElementMoveToPoint))[bezierPointsaddObject:VALUE(1)];}if(type==kCGPathElementAddCurveToPoint)[bezierPointsaddObject:VALUE(2)];}NSArray*pointsFromBezierPath(UIBezierPath*bpath){NSMutableArray*points=[NSMutableArrayarray];//获取贝塞尔曲线上所有的点CGPathApply(bpath.CGPath,(__bridgevoid*)points,getPointsFromBezier);returnpoints;}-(UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularitypath:(UIBezierPath*)path{NSMutableArray*points=[pointsFromBezierPath(path)mutableCopy];if(points.count*sublayers;//子图层变换支持隐式动画@propertyCATransform3DsublayerTransform;//图层蒙版支持隐式动画@property(nullable,strong)__kindofCALayer*mask;//子图层是否裁切超出父图层的部分,默认为NO@propertyBOOLmasksToBounds;//图层显示内容设置layer的contents可以为layer添加显示内容支持隐式动画@property(nullable,strong)idcontents;//图层显示内容的大小和位置支持隐式动画@propertyCGRectcontentsRect;//用于指定层的内容如何在其范围内定位或缩放@property(copy)CALayerContentsGravitycontentsGravity;//是否包含完全不透明内容的布尔值@property(getter=isOpaque)BOOLopaque;//背景色支持隐式动画@property(nullable)CGColorRefbackgroundColor;//圆角半径支持隐式动画@propertyCGFloatcornerRadius;//边框宽度支持隐式动画@propertyCGFloatborderWidth;//边框颜色支持隐式动画@property(nullable)CGColorRefborderColor;//透明度支持隐式动画@propertyfloatopacity;//阴影颜色支持隐式动画@property(nullable)CGColorRefshadowColor;//阴影透明度默认为0需要显示阴影必须设置值支持隐式动画@propertyfloatshadowOpacity;//阴影偏移量支持隐式动画@propertyCGSizeshadowOffset;//阴影半径支持隐式动画@propertyCGFloatshadowRadius;//阴影形状支持隐式动画@property(nullable)CGPathRefshadowPath;3.1.2 子类CALayer的子类有很多,下面说几个比较常用的。3.2 CAShapeLayer苹果官网注释:“A layer that draws a cubic Bezier spline in its coordinate space.” 专门用于绘制贝塞尔曲线的layer。3.2.1 看一下它独特的属性:// path属性是曲线的路径,也是它和贝塞尔曲线紧密连接一个入口,决定了图层上画的是什么形状。@property(nullable)CGPathRefpath;//填充颜色@property(nullable)CGColorReffillColor;//曲线指定哪块区域为内部,内部会被填充颜色@property(copy)CAShapeLayerFillRulefillRule;//线的颜色@property(nullable)CGColorRefstrokeColor;//strokeStart和strokeEnd两者的取值都是0~1,决定贝塞尔曲线的划线百分比@propertyCGFloatstrokeStart;@propertyCGFloatstrokeEnd;//虚线开始的位置@propertyCGFloatlineDashPhase;//虚线设置,数组中奇数位实线长度,偶数位带遍空白长度@property(nullable,copy)NSArray*lineDashPattern;//线的宽度@propertyCGFloatlineWidth;//最大斜接长度只有lineJoin属性为kCALineJoinMiter时miterLimit才有效@propertyCGFloatmiterLimit;//线端点样式(样式与贝塞尔曲线的CGLineCap属性一致)@property(copy)CAShapeLayerLineCaplineCap;//拐角样式(样式与贝塞尔曲线的CGLineJoin属性一致)@property(copy)CAShapeLayerLineJoinlineJoin;3.2.2 举个栗子使用上面的一些属性,再结合贝塞尔曲线,我们实现了如下一些效果:其中图五的效果,代码实现如下:UIBezierPath*maskPath=[UIBezierPathbezierPath];for(NSIntegeri=1;i*locations;//决定了变色范围的起始点@propertyCGPointstartPoint;//决定了变色范围的结束点@propertyCGPointendPoint;//startPoint和endPoint两者的连线决定变色的趋势3.3.2 举个栗子使用上面的一些属性我们实现了如下一些效果:其中图五的效果,代码实现如下:CAGradientLayer*gradientLayer=[CAGradientLayerlayer];gradientLayer.frame=CGRectMake(20,450,150,150);gradientLayer.locations=@[@(0.2),@(0.5),@(0.6),@(0.8)];gradientLayer.startPoint=CGPointMake(0,0);gradientLayer.endPoint=CGPointMake(1,1);gradientLayer.colors=@[(id)[UIColorpurpleColor].CGColor,(id)[UIColorgreenColor].CGColor,(id)[UIColororangeColor].CGColor,(id)[UIColorblackColor].CGColor];[self.view.layeraddSublayer:gradientLayer];3.4 再举个栗子当CAGradientLayer + CAShapeLayer + 贝塞尔曲线 会有什么效果?上代码~-(void)setupUI{//贝塞尔曲线UIBezierPath*maskPath=[UIBezierPathbezierPath];[maskPathmoveToPoint:CGPointMake(100,220)];[maskPathaddLineToPoint:CGPointMake(200,150)];[maskPathaddLineToPoint:CGPointMake(300,220)];[maskPathstroke];UIBezierPath*maskBottomPath=[UIBezierPathbezierPath];[maskBottomPathmoveToPoint:CGPointMake(280,250)];[maskBottomPathaddCurveToPoint:CGPointMake(120,250)controlPoint1:CGPointMake(250,320)controlPoint2:CGPointMake(150,320)];[maskBottomPathstroke];[maskPathappendPath:maskBottomPath];//CAShapeLayerCAShapeLayer*maskLayer=[[CAShapeLayeralloc]init];maskLayer.frame=self.view.bounds;maskLayer.path=maskPath.CGPath;maskLayer.lineWidth=20;maskLayer.strokeColor=UIColorFromRGB(0xF0F5FF).CGColor;maskLayer.lineCap=kCALineCapRound;maskLayer.lineJoin=kCALineJoinRound;maskLayer.fillColor=[UIColorclearColor].CGColor;maskLayer.strokeStart=0;maskLayer.strokeEnd=0;[self.view.layeraddSublayer:maskLayer];//CAGradientLayerNSMutableArray*colorArray=[NSMutableArraynew];for(NSIntegeri=0;i
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 01:52 , Processed in 1.005287 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表