扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
先说下背景,项目里需要绘制音乐和视频的波形图,由于产品上的设计,波形图的长度基本都可以达到屏幕长度的几十倍。并且图形并不是折线图而是柱状图,还要跟随音乐音量变化,所以图形肯定是无法直接拉伸挤压的,所以当时为了性能和内存方面的考虑,尝试了很多方案。
创新互联是一家集网站建设,岐山企业网站建设,岐山品牌网站建设,网站定制,岐山网站建设报价,网络营销,网络优化,岐山网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
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中绘图的三种方式
定义: Core Graphics 是一套基于C语言的API框架.使用 Quartz绘图引擎.
基本概念: 图形上下文 CGContentRef
考虑到效率方面上的问题,通常我们不会创建。直接获取
通过
获取图形上下文
默认情况下返回值为 nil.在调用这个方法之前调用
方法,视图将创建一个绘图上下文,并且设置为 the current context(当前上下文,也就是绘图图层栈栈顶的上下文).这个上下文context只会在 drawRect: 方法中存在.一旦调用此方法调用完成,这个上下文 context 将会被销毁.
如果你不是使用UIView去绘制,这时候你需要手动往栈顶入一个。使用下面的方法
这个方法可以在app中的任何线程中调用.
CGPath 也就是路径信息。CGPath可以确定 绘制边框的各种属性(颜色 粗细 拐点),边框内部区域的绘图属性, 绘制边框还是绘制边框内部
创建路径
在绘制路径时
需要指定绘制路径时模式
CGAffineTransform 是一个结构体
系统已经帮我封装好了一下方法
用于绘制虚线
由于项目需要用到k线图,但是在网上搜索了很多都不太理想,大概看了一下,理了一下思路决定自己写。这些都是使用最简单的画图写出来的,并没有那么多高深的东西。
k线图简单来说有三点:
使用以下触摸方法来控制位移
首先要计算出手指移动的 距离 、 方向 ,根据距离来确定移动了多少个元素,使用代理方法 LSSKLineViewDelegate 在vc里进行数据的操作
终于找到之前的项目了,由于是4年前的项目,整体比较乱,也有一些bug,暂时供参考思路,稍后可能重构加注释之类的吧
DEMO
这种绘制是根据图片的像素比例 等比例进行绘制的,在选择图片和创建展示图片的imageView 时,注意查看尺寸 注:绘图时使用 [UIScreen mainScreen].scale 可以是图片更清晰 UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
//图片上添加文字 详细版
- (UIImage*)text:(NSString*)text addToView:(UIImage*)image{
//设置字体样式
UIFont*font = [UIFont fontWithName:@"Arial-BoldItalicMT"size:100];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:text];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 1)];
[str addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:100] range:NSMakeRange(0, text.length)];
// CGSize textSize = [text sizeWithAttributes:dict];
CGSize textSize = [str size];
//绘制上下文
UIGraphicsBeginImageContext(image.size);
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
[image drawInRect:CGRectMake(0,0, image.size.width, image.size.height)];
// int border =10;
CGRect re = {CGPointMake((image.size.width- textSize.width)/2, 200), textSize};
// CGRect rect = CGRectMake(0, 0, image.size.width, 500);
//此方法必须写在上下文才生效
[str drawInRect:re ];
UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
//修改图片尺寸
- (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize
{
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);//这样压缩的图片展示出来会很模糊
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
// Return the new image.
return newImage;
}
//圆角
- (UIImage *) getRadioImaeg:(NSString *)imageName1{
UIImage *image1 = [UIImage imageNamed:imageName1];
UIGraphicsBeginImageContextWithOptions(image1.size, 0, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(00, 0, image1.size.width, image1.size.width);
CGContextAddEllipseInRect(ctx, rect);
CGContextClip(ctx);
[image1 drawInRect:rect];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
//图片叠加
- (UIImage *)addImage:(NSString *)imageName1 withImage:(NSString *)imageName2 {
UIImage *image1 = [UIImage imageNamed:imageName1];
UIImage *image2 = [self getRadioImaeg:@"333"];
UIGraphicsBeginImageContext(image1.size);
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake((image1.size.width - image2.size.width)/2,(image1.size.height - image2.size.height)/2, image2.size.width, image2.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}
即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 中坐标系的原点在左上角,而 Quartz 2D 的坐标系的原点在左下角。
它是一个二维(二维即平面)绘图引擎(封装的一套用于绘图的函数库),同时支持iOS和Mac系统(可以跨平台开发)。 API (应用程序界面)是纯C语言的,来自于 Core Graphics 框架,其数据类型和函数基本都以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 递归查询图层是否有布局上的更新= CALayer layoutSublayers = UIView layoutSubviews 。从这里调用的流程也可以看出 UIView 其实就是相当于 CALayer 的代理。
用四种方法的目的是说明绘制图形有很多种方法。绘制图形实际上就是设置 path ,底层的用的都是 CGMutablePathRef 。使用贝塞尔曲线画图的好处在于,每一个贝塞尔底层都有一个图形上下文,如果是用 CGContextMoveToPoint 画图,实际上就是一个图形上下文,不好去控制,所以建议多条线可以使用贝塞尔曲线。不推荐使用第4种方式,两个东西杂糅,不太好。
实现图片的水印、裁剪、截屏、压缩等效果,这里以压缩图片为例,其余步骤类似。
CAShapeLayer 是 CALayer 的子类,多用于处理复杂的边缘涂层和边缘动画,虽然该对象也有 frame 属性,但其
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流