前言:现在做金融的越来越多了,在很多的技术群中都有人问到k线图怎么去做,有没有相关的框架?两年前,我刚入这金融公司也是走这条路,但是发现网上的框架不多,干脆就自己搞一个出来。没人分享相关知识,就分享下绘图心得好了,大家一起探讨优化。
一、理论知识
1.用什么去绘制k线图?
在移动端、前端这边,绘图从来都不是什么大事,包括接下来的小程序。大多数都是把相关的东西提交给系统去做!所以不要怕绘图!在iOS中,我们经常会用到CoreGraphics上下文渲染,或者用CAShapelayer配合UIBezierPath去做。当然,本文不是教你怎么用API,我不会去侮辱程序员的。。
卡顿:CPU计算的内容太多 来不及提交新的东西给GPU渲染显示 它还是弄旧的..
UIBezierPath:绘制路径的,绘制出来的路径可以给上下文环境(drawrect系列方法)渲染或者CAShapelayer渲染。上下文环境对资源的消耗较大 一不小心就离屏渲染或者各种内存问题(我也是听来的 实际上使用貌似没多大消耗 不知道是不是苹果改了还是我用对API了)上下文语法不熟悉的也得看一会才能好好用,所以我们介绍CAShapelayer为主.
2.坐标系?
iOS的坐标系由上往下,左往右,这大家都懂。。但是我要说的是走势图的坐标系!!
绘图我们肯定要知道x,y坐标,我们在外部传入这个view的位置大小之后,我们的x坐标就基本确定下来了。比如我用width:320,那么我们可以确定320个,当然这个可以按照需求。一般我走势图数据量大的股票期货都会默认为1,其它的大于一,当然,这是可以缩放的。
y坐标:这个试图的高我们一定确定好了。我们现在是要知道这个高每一点代表的值是多少?比如我现在这个股票,最高点40,最低点30,现价32,高度100,那么我们每一点代表0.1,然后这个是第一个数据,所以它的对应坐标是(0,20)? 呵呵,答错了!虽然是这个没错,但是我们看盘的时候是从下往上看的嘛,所以它应该是(0,80)的位置(下方往上为20);
3.耗电耗性能?
一开始我们的版本是用上下文去渲染显示的,但是由于考虑不周,被用户投诉耗电耗流量非常多。然后就开始操刀优化了。
说点无关的:首先解决的事耗流量的问题。我们的数据都是用TCP去推送的,由于之前的某些原因,数据的组合格式过于浪费,后来改为按需拉取,于是就解决了。==! TCP是个好东西,即时性高,能主动推送数据不丢包,应用在前台还能绕过APNS,微信的聊天貌似也用TCP+Http。当然即时性不高的可以用轮询+设置超时90s..你懂的!
那耗电量是什么回事?
我们知道iOS耗电高的 就是不断的循环操作、CPU GPU转啊转。。 知道这些就好办了,优化算法减少计算量,用enumerateObjectsUsingBlock取代forin。。当然这些你肯定知道的!但是要解决这个耗电量得知道它还有什么业务操作才行。。 由于期货股票这东西有时候拨动的非常快,一秒几次,可能这时候我们已经有上千点数据准备去画了,一秒算三次,那么CPU过的非常充实哦? 我们当然不可以这么做。。
那么如何去优化它?
我们都知道,iOS的view都是使用组合模式的,基类的写好公用的(大概就是模板模式那理论啦),然后继承这个类,然后addSubview..remove..,那么我们可以从中得到什么信息? 那就是分层。。 这也告诉了我们 研究系统API的设计 规范化开发是多么重要..(至少会减少逻辑bug),顺便带一句,我和别人聊天的时候都会问block和delegate的应用场景。。 大部分人都局限于传值..这貌似是培训班的套路?? 看看第三方框架或者分析系统框架?
那我们知道这些东西我们差不多可以开始动工了。
我们知道要做好绘制好走视图,需要分层,那怎么分? 单一职责帮助你。
说一说我们的需求。
1.显示走势图(废话)
2.拉动显示其它数据
3.十字光标滑动的时候 出现左边或者右边的对应的点或者蜡烛的详情(开收盘等) 也有些公司会用点击某根蜡烛弹出详情
4.缩放功能。 暂时就这么多了 我也不知道还要示范些啥?
二、动工
1.确定好需要外界提供什么材料?
想想就有点小激动了。。 外界? 当然是委托别人去做的啦。。 先写好一个协议。我看那个蜡烛图怎么和collectionView那么像啊? 那我的API也和它差不多就好啦。 好,确定好我需要外界给我东西,就是显示的个数,还有现在显示的范围。。
设计API 就是要简单粗暴。。 太难用了自己都不想用啦!
我们知道,现在是在实现基础的走视图 不包含其它的
简单些:-(NSInteger)numberOfView:(UIView *)view;
- (KlineModel *)LineView:(UIView *)view cellAtIndex:(NSInteger)index;
那我们怎么确定index数据?
我们已经获取到了这个视图的宽度了,这时候分情况。1.分时图:自定义或者默认1;2:k线图,我们先在上方写好:
static const NSInteger KlineCellSpacing = 2;//cell间隔
static const NSInteger KlineCellWidth = 6;//cell宽度
static const NSInteger CellOffset = 1;//偏移的单位
这个时候,我们已经清楚的知道 宽度为6 间隔为2,那么一个单位就是8.. 我有320,那就能画40个咯? 假设代理给我的个数是1200 那我只需要拿1161-1200。 那就回调它 开始获取数据制作k线图了。
当然,这也是需要分两种情况的,分时图太简单了 就是一条线,那我们拿k线图来说。 制作原理一样。
2.开始绘图
绘制基础显示图
假设这个时候 我们已经拿到40个数据了。我们先遍历它,获得最高最低点,获得每个点代表的值,当然我们要在外面写好willXXX等方法告诉外面 我们知道这个最高价最低价是多少了,顺便放出这两个只读属性(业务需求)。这个时候 我们有两种数据类型,一种是永远不变的(至少没突破最高最低值的时候不会变),一种是会变动的。每次Socket推送过来的数据我们都要及时的更新为视图。那我们开始分层了!
先新建一个CAShapelayer属性Shapelayer,把刚刚的所有数据通过UIBezierPath 转化为路径 加载在它这边,多个CAShapelayer都加载在这里面。CAShapelayer也是组合模式的。add就好了 设置好每一个的颜色各种形 一个一个加。(这里说的是k线图 曲线图就一个好了 我也是取巧的方法 = =!)
如果直接加载在这个Shapelayer属性,这里就有个问题了..颜色怎么办?这个是很重要的,毕竟涨跌一个点都要钱啊。。之前我有尝试过用渐变去给一个CAShapelayer染色,但是..惨不忍睹、不忍再提。。 不过还好 对性能没影响,估计是底层苹果把它给合并的吧?这个需要点时间去研究...但是最近又有新项目了 貌似还有接到一个外包?? 这两个问题希望大神能赐教 感激不尽。
跑调了啊 =。=
好吧。画完上面的东西了,我们开始画变的那一层。先定好API 确保API容易用(容不容易去跑下单元测试),这个API传入一个model。 这个时候 我们已经有个Shapelayer,我们拿到它的sublayer数组 删掉最后一个。我们已经有每点代表的值了,直接转化显示就好了。但是,我们不确保它的值不会我们的最高最低值。那我们比较一下,如果超越了,那就替换刚刚获得的数组最后一个model 然后调用刚刚的计算方法 从那一步开始重新走一遍,重新计算。这个过程就要看你的API设计的是不是职责单一了。 = =!封装好每一个关联的面向过程实现吧?
基础绘图的介绍就到这里好了。接下来是拉动效果!
拉动移动显示其它数据
这个也没什么好说的。我们继承的是view对吧? 加个手势或者截取响应链。我这边是用手势!刚刚上面我们已经有一个CellOffset的常量了。在手势中通过translationInView获取的x正负判断方向 加减我们的OffsetIndex(一开始是从1161开始 看上文)。然后移动以后 判断它是不是合法下标 重新获取数据 继续调用计算方法 走流程 。= =! 问题:我一直在想,要保持好这些绘图对象像CollectionView那样子 还是直接重新绘制比较省资源。。 这点还是可以继续优化的。
滑动的时候 联动左边或者右边的对应的点或者蜡烛的详情(开收盘等)
这个时候 我们需要新建一个ShowTrackingCross,让外界控制是否显示十字光标,还是拉动显示其它数据.. 这个时候 我们可以封装一个CALayer类 把触摸点传进一个方法里面 逆推得出对应数组的index而得到model,计算最新价的y 就可以得出这个点。在代理里面写个可选实现方法把它传递出去并绘制这个十字光标,再写个关闭十字光标的方法让使用者或者自己可以把它移除。
缩放功能
= 。= 定义好scale属性,初始化为1。在计算的时候参与x,width的计算。在缩放手势的时候改变它的大小。。 这里要注意在计算显示的个数也是要参与计算的= =!
其它细节
旋转屏幕等操作需要注意重新setFrame。均线 等其它的也是分层好了,闪电图直接画? 暂时就先到这里了 大致的说一遍。这几天整理个demo再从实现说起应该比较好说一点!下次还要探讨socket等网络编程。希望各位大神指出存在的逻辑繁琐问题 bug 优化。只是demo 求大神带上开发对应组件! 跪谢。