先说下背景,项目里需要绘制音乐和视频的波形图,由于产品上的设计,波形图的长度基本都可以达到屏幕长度的几十倍。并且图形并不是折线图而是柱状图,还要跟随音乐音量变化,所以图形肯定是无法直接拉伸挤压的,所以当时为了性能和内存方面的考虑,尝试了很多方案。
UIImage:
使用UIGraphicsGetImageFromCurrentImageContext方法将绘制的图形生成图片。
这种方式适用于图片不长并且图片不变的情况。
优点:可在子线程绘制,方便缓存。
缺点:占用内存大,绘制不够高效。
PS:注意此方法有个隐患,因为系统会对设置给UIImageView的图片进行缓存,如果一直调用,即使是完全相同的图片,也会产生内存占用。
示例:
CALayer:
layer的基类,重写drawInContext方法进行进行绘制。
优点:量级轻(实在想不到优点)。
缺点:主线程绘制,绘制不够高效。
CATiledLayer:
layer的子类,专门用于绘制大图的方案,系统底层已进行过优化,子线程绘制,并且不会绘制屏幕外的内容。可将大图分割成若干个更小的单元进行绘制,可通过tileSize设置单个绘制单元的大小。
优点:子线程绘制,性能极好。
缺点:绘制较缓慢,能控制的变量较少。
与CALayer用法一致,可设置额外属性
YYAsyncLayer:
知名的异步绘制的第三方控件,是CALayer的子类,内部创建了队列进行管理,能将绘制的操作转换为异步操作,并且引入了RunLoop机制进行管理,只在RunLoop空闲的时候才进行刷新操作。
优点:子线程绘制,性能很高,不阻塞用户操作。
缺点:因为只在空闲时执行,图形刷新不及时。
CAShapeLayer+UIBezierPath:
layer的子类,CAshapeLayer能在GPU上渲染,性能很高,绘制速度很快,而且曲线绘制既能选择在主线程绘制,也能选择在子线程绘制。
优点:无论子线程还是主线程都可绘制,且性能很高。
缺点:曲线路径量大的话,还是影响性能。
最终还是选择了CAShapeLayer+UIBezierPath方案,但是因为音波数据量特别大(每秒40个音频数据),导致多个图形频繁刷新的时候发热十分严重,而且也出现了卡顿的情况。
为了优化,最后又加入了分屏绘制的逻辑:
蓝色区域表示屏幕区域,红色表示绘制的区域,黑色线条表示临界边,
整体逻辑:
0、转换坐标为窗体坐标。
1、判断是否有上次绘制的位置,没有则直接绘制。
2、绘制完成后保存当前位置为绘制位置,计算出黑色临临界区域。
3、滑动视图的过程中判断滑动位置是否超出了黑线区域,超出则重新进行绘制。
4、重复2、3。
IOS中绘图的三种方式
定义:CoreGraphics是一套基于C语言的API框架.使用Quartz绘图引擎.
基本概念:图形上下文CGContentRef
考虑到效率方面上的问题,通常我们不会创建。直接获取
通过
获取图形上下文
默认情况下返回值为nil.在调用这个方法之前调用
方法,视图将创建一个绘图上下文,并且设置为thecurrentcontext(当前上下文,也就是绘图图层栈栈顶的上下文).这个上下文context只会在drawRect:方法中存在.一旦调用此方法调用完成,这个上下文context将会被销毁.
如果你不是使用UIView去绘制,这时候你需要手动往栈顶入一个。使用下面的方法
这个方法可以在app中的任何线程中调用.
CGPath也就是路径信息。CGPath可以确定绘制边框的各种属性(颜色粗细拐点),边框内部区域的绘图属性,绘制边框还是绘制边框内部
创建路径
在绘制路径时
需要指定绘制路径时模式
CGAffineTransform是一个结构体
系统已经帮我封装好了一下方法
用于绘制虚线
由于项目需要用到k线图,但是在网上搜索了很多都不太理想,大概看了一下,理了一下思路决定自己写。这些都是使用最简单的画图写出来的,并没有那么多高深的东西。
k线图简单来说有三点:
使用以下触摸方法来控制位移
首先要计算出手指移动的距离、方向,根据距离来确定移动了多少个元素,使用代理方法LSSKLineViewDelegate在vc里进行数据的操作
终于找到之前的项目了,由于是4年前的项目,整体比较乱,也有一些bug,暂时供参考思路,稍后可能重构加注释之类的吧
DEMO
这种绘制是根据图片的像素比例等比例进行绘制的,在选择图片和创建展示图片的imageView时,注意查看尺寸注:绘图时使用[UIScreenmainScreen].scale可以是图片更清晰UIGraphicsBeginImageContextWithOptions(image.size,NO,[UIScreenmainScreen].scale);//这样就不模糊了
//图片上添加文字详细版
-(UIImage*)text:(NSString*)textaddToView:(UIImage*)image{
//设置字体样式
UIFont*font=[UIFontfontWithName:@"Arial-BoldItalicMT"size:100];
NSMutableAttributedString*str=[[NSMutableAttributedStringalloc]initWithString:text];
[straddAttribute:NSForegroundColorAttributeNamevalue:[UIColorblueColor]range:NSMakeRange(0,1)];
[straddAttribute:NSFontAttributeNamevalue:[UIFontsystemFontOfSize:100]range:NSMakeRange(0,text.length)];
//CGSizetextSize=[textsizeWithAttributes:dict];
CGSizetextSize=[strsize];
//绘制上下文
UIGraphicsBeginImageContext(image.size);
//UIGraphicsBeginImageContextWithOptions(image.size,NO,[UIScreenmainScreen].scale);//这样就不模糊了
[imagedrawInRect:CGRectMake(0,0,image.size.width,image.size.height)];
//intborder=10;
CGRectre={CGPointMake((image.size.width-textSize.width)/2,200),textSize};
//CGRectrect=CGRectMake(0,0,image.size.width,500);
//此方法必须写在上下文才生效
[strdrawInRect:re];
UIImage*newImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
returnnewImage;
}
//修改图片尺寸
-(UIImage*)imageWithImageSimple:(UIImage*)imagescaledToSize:(CGSize)newSize
{
//Createagraphicsimagecontext
UIGraphicsBeginImageContext(newSize);//这样压缩的图片展示出来会很模糊
//Telltheoldimagetodrawinthisnewcontext,withthedesired
//newsize
[imagedrawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
//Getthenewimagefromthecontext
//Endthecontext
//Returnthenewimage.
//圆角
-(UIImage*)getRadioImaeg:(NSString*)imageName1{
UIImage*image1=[UIImageimageNamed:imageName1];
UIGraphicsBeginImageContextWithOptions(image1.size,0,0);
CGContextRefctx=UIGraphicsGetCurrentContext();
CGRectrect=CGRectMake(00,0,image1.size.width,image1.size.width);
CGContextAddEllipseInRect(ctx,rect);
CGContextClip(ctx);
[image1drawInRect:rect];
UIImage*img=UIGraphicsGetImageFromCurrentImageContext();
returnimg;
//图片叠加
-(UIImage*)addImage:(NSString*)imageName1withImage:(NSString*)imageName2{
UIImage*image2=[selfgetRadioImaeg:@"333"];
UIGraphicsBeginImageContext(image1.size);
[image1drawInRect:CGRectMake(0,0,image1.size.width,image1.size.height)];
[image2drawInRect:CGRectMake((image1.size.width-image2.size.width)/2,(image1.size.height-image2.size.height)/2,image2.size.width,image2.size.height)];
UIImage*resultingImage=UIGraphicsGetImageFromCurrentImageContext();
returnresultingImage;
即UIBezierPath在drawRect中的使用,在实际中,我们多使用UIBezierPath来自定义视图。
效果
UIBezierPath与CAShapeLayer的结合,是我们开发中最常见的,功能很强大。
CAShapeLayer与UIBezierPath的关系:
CAShapeLayer中shape代表形状的意思,所以需要形状才能生效
贝塞尔曲线可以创建基于矢量的路径,而UIBezierPath类是对CGPathRef的封装
贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染。路径会闭环,所以绘制出了Shape
用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线
例如设置圆角:
自定义视图
位图(Bitmap),又称点阵图,是使用像素阵列来表示的图像。位图的像素都分配有特定的位置和颜色值。每个像素的颜色信息由RGB组合或者灰度值表示。根据位深度可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,相应的数据量越大。例如,位深度为1的像素位图只有两个可能的值(黑色和白色),所以又称为二值位图。位深度为8的图像有2(即256)个可能的值。位深度为8的灰度模式图像有256个可能的灰色值。RGB图像由三个颜色通道组成。8位通道的RGB图像中的每个通道有256个可能的值,这意味着该图像有1600万个以上可能的颜色值。有时将带有8位通道(bpc)的RGB图像称作24位图像。通常将使用24位RGB组合数据位表示的的位图称为真彩色位图。
由上面的描述可知,我们可以将bitmap理解为一个点阵图或者是一个数组,其中的每个元素都是一个像素信息,假设对于一个32位RGBA图像来说,则每个元素包含着三个颜色组件(R,G,B)和一个Alpha组件,每一个组件占8位(8bite=1byte=32/4)。这些像素集合起来就可以表示出一张图片。
Bitmap的数据由CGImageRef封装。由以下几个函数可以创建CGImageRef对象。
如果要使用bitmap对图片进行各种处理,则需要先创建位图上下文。先看一下初始化方法的一个例子。
它是高级别的图形接口,它的API都是基于Objective-C的。它能够访问绘图、动画、字体、图片等内容。UIkit中坐标系的原点在左上角,而Quartz2D的坐标系的原点在左下角。
它是一个二维(二维即平面)绘图引擎(封装的一套用于绘图的函数库),同时支持iOS和Mac系统(可以跨平台开发)。API(应用程序界面)是纯C语言的,来自于CoreGraphics框架,其数据类型和函数基本都以CG作为前缀。
它是用来设置当前的layer在父控件当中的位置的,默认以父控件左上角的(0.0)点为它的坐标原点。
它决点CALayer身上哪一个点会在position属性所指的位置。anchorPoint是以当前的layer左上角为原点(0.0),取值范围是0~1,默认在中间也就是(0.5,0.5)的位置。
调用会重新绘制整个视图,此时系统会自动帮你调用drawRect方法。
重新绘制视图的部分区域。最好不要绘制视图的全部,以减少绘制带来开销。
标记为需要重新布局,会异步调用layoutIfNeeded刷新布局。不会立即刷新,而是在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次。
修改了当前视图的size、设置了不同的frame或者调用了addsubViews,都是会被系统自动给你标记为setNeedsLayout的,然后调用layoutSubviews进行重新布局。
如果发现有需要刷新的标记,立即调用layoutSubviews进行布局。如果想在当前runloop中立即刷新,调用顺序应该是:
将继承于UIView的子类进行布局更新来刷新视图。如果某个视图自身的bounds或者子视图的bounds发生改变,那么这个方法会在当前runloop结束的时候被调用。为什么不是立即调用呢?因为渲染毕竟比较消耗性能,特别是视图层级复杂的时候。在这种机制下任何UI控件布局上的变动不会立即生效,而是每次间隔一个周期,所有UI控件在布局上的变动统一生效再在视图上一起更新,苹果通过这种高性能的机制保障了视图渲染的流畅性。
runloop的observer回调=CoreAnimation渲染引擎一次事务的提交=CoreAnimation递归查询图层是否有布局上的更新=CALayerlayoutSublayers=UIViewlayoutSubviews。从这里调用的流程也可以看出UIView其实就是相当于CALayer的代理。
用四种方法的目的是说明绘制图形有很多种方法。绘制图形实际上就是设置path,底层的用的都是CGMutablePathRef。使用贝塞尔曲线画图的好处在于,每一个贝塞尔底层都有一个图形上下文,如果是用CGContextMoveToPoint画图,实际上就是一个图形上下文,不好去控制,所以建议多条线可以使用贝塞尔曲线。不推荐使用第4种方式,两个东西杂糅,不太好。
实现图片的水印、裁剪、截屏、压缩等效果,这里以压缩图片为例,其余步骤类似。
CAShapeLayer是CALayer的子类,多用于处理复杂的边缘涂层和边缘动画,虽然该对象也有frame属性,但其