cad渐变线怎么画_花花绿绿的股票线是怎么画出来的?想怎么画就怎么画!
点击上方 "程序员小乐"关注, 星标或置顶一起成长
每天凌晨00点00分, 第一时间与你相约
往日回顾: 鸿蒙开发初体验
正文
股票??数字货币??都是浮云,没那智商还是好好撸代码吧!今天作为一个嫩绿嫩绿的韭菜,就来用技术征服一下割过自己的股票行情图。
股票行情图中比较复杂的应该当属于蜡烛线(阴阳线),这块手势处理复杂、图表指标复杂、交互复杂、数据处理复杂......总之:复杂!
所以就从今天开始我从0到1打造出这个复杂的行情图!费话不多说,上图!上链接:
github地址:
github.com/SlamDunk007/StockChart
/ 绘制流程 /
整个绘制过程完全自定义View不依赖任何第三方绘制工具,大概分为三个部分:具体的绘制过程、手势的处理、数据的处理。下面就从这三个方面逐个进行讲解。
搜索公众号程序员小乐回复关键字“offer”,获取算法面试题和答案。
具体绘制过程
这里使用的是Android的canvas进行绘制的,android的canvas真的是特别的强大,为了调高绘制效率,我在这里的绘制进行了修改:提前创建一个Canvas和Bitmap,然后在子线程当中进行绘制:
private void initCanvas() {
repeatNum = 0;if (mRealCanvas == null) {
mRealCanvas = new Canvas();
Bitmap curBitmap =
createBitmap(mViewPortHandler.getChartWidth(), mViewPortHandler.getChartHeight(),
Bitmap.Config.ARGB_8888);
Bitmap alterBitmap = curBitmap.copy(Bitmap.Config.ARGB_8888, true);if (curBitmap != null && alterBitmap != null) {
mRealCanvas.setBitmap(curBitmap);
mCurBitmap = curBitmap;
mAlterBitmap = alterBitmap;
}
}
}
接下来采用双缓冲的绘图机制,先在子线程当中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是全部绘制完之后一次性显示到屏幕。
/**
* 进行具体的绘制
*/class DoubleBuffering implements Runnable {private final WeakReference mChartView;public DoubleBuffering(BaseChartView view) {
mChartView = new WeakReference<>(view);
}@Overridepublic synchronized void run() {if (mChartView != null) {
BaseChartView baseChartView = mChartView.get();if (baseChartView != null && baseChartView.mRealCanvas != null) {
baseChartView.drawFrame(baseChartView.mRealCanvas);
Bitmap bitmap = baseChartView.mCurBitmap;if (bitmap != null && baseChartView.mHandler != null) {
baseChartView.mHandler.sendEmptyMessage(baseChartView.REFRESH);
}
}
}
}
}
然后将我们绘制完成的bitmap对象交给View的onDraw()方法的canvas去绘制
@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mRealBitmap != null) {
canvas.drawBitmap(mRealBitmap, 0, 0, mPaint);
}if (hasDrawed) {
hasDrawed = false;if (!mHandler.hasMessages(START_PAINT)) {
Message message = new Message();
message.what = START_PAINT;
message.obj = mDoubleBuffering;
mHandler.sendMessageDelayed(message, 25);
}
}
}
这是整个绘制流程的关键代码,和平时的自定义绘制没有什么特殊的区别,只不过这里采用了双缓冲的绘图机制。提前绘制到一个Bitmap上去。
我做过一个简单的测试,当绘制的视图比较复杂的时候,如果提前进行绘制,打开开发者的呈现模式,可以发现越复杂的视图,对GPU的消耗减少的越明显,这里大家可以写一个demo简单测试一下,这里不再赘述。
蜡烛线、长按十字线和长按弹框的具体绘制
长按手势的识别方法可以继续参考下面的手势的处理部分。
蜡烛线:股票的蜡烛线有高、开、低、收四个参数,分别代表:最高价、开盘价、最低价、收盘价。这里首先计算出最高价当中的最大值和最低价当中的最小值,然后根据(maxPrice - openPrice)/diffPrice,计算出蜡烛线的上影线,下影线,开盘价,收盘价的占比。从而就能计算出在绘制区域的具体位置。
// 计算蜡烛线float scaleY_open = (maxPrice - open) / diffPrice;float scaleY_low = (maxPrice - close) / diffPrice;
RectF candleRect = getRect(contentRect, k, scaleY_open, scaleY_low);
drawItem.rect = candleRect;// 计算上影线,下影线float scale_HL_T = (maxPrice - high) / diffPrice;float scale_HL_B = (maxPrice - low) / diffPrice;
RectF shadowRect = getLine(contentRect, k, scale_HL_T, scale_HL_B);
drawItem.shadowRect = shadowRect;
长按十字线和弹框:这个是根据长按的动作然后在右上角的位置,获取最后一天的高开低收等数据,最后重新绘制当前屏幕。
// 绘制长按十字线if (mFocusPoint != null && onLongPress) {if (contentRect.contains(mFocusPoint.x, mFocusPoint.y)) {
canvas.drawLine(contentRect.left, mFocusPoint.y, contentRect.right, mFocusPoint.y,
PaintUtils.FOCUS_LINE_PAINT);
}
canvas.drawLine(mFocusPoint.x, contentRect.top, mFocusPoint.x, contentRect.bottom,
PaintUtils.FOCUS_LINE_PAINT);
KLineToDrawItem item = mToDrawList.get(mFocusIndex);
drawBollDes(canvas, contentRect, item);
}// 长按显示的弹框
showLongPressDialog(canvas, contentRect);
手势的处理
代码当中的ChartTouchHelper是处理手势的关键类,目前行情图的手势有几种:左右滑动DRAG、惯性滑动FLING、放大缩小Scale、长按LONG_PRESS。
搜索公众号程序员小乐回复关键字“Java”,获取Java面试题和答案。
这里使用了android当中的GestureDetectorCompat结合onTouch(View v, MotionEvent event)来处理这几种手势。
左右滑动DRAG
实现OnGestureListener接口,有一个onScroll的方法,在这里将X轴移动的距离当做偏移量,一屏默认显示的蜡烛线是60个,根据偏移量可以计算出移动了多少个蜡烛线,然后就能根据这个去计算下一次绘制的起始点的位置,重新计算滑动后的屏幕的数据。最后Invalidate一下,重新进行绘制即可。
/**
* @param e1 down的时候event
* @param e2 move的时候event
* @param distanceX x轴移动距离:两个move之间差值
* @param distanceY y轴移动距离
*/@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {if (mChartGestureListener != null) {
scrollX -= distanceX;// 当X轴移动距离大于18px认为是移动if (Math.abs(scrollX) > mXMoveDist) {
mChartGestureListener.onChartTranslate(e2, scrollX);
scrollX = 0;
}
}if (Math.abs(distanceX) > Math.abs(distanceY)) {return true;
} else {return false;
}
}
惯性滑动FLING
当手指快速滑动离开的那一瞬间,有一个初始速度。通过SensorManager计算出加速度,根据公式a=V^2/2S(加速度等于最大速度的平方除以2倍的路程),可以反推出S=V^2/2a,计算出加速度减为0的时候,总共Fling的距离。这里默认是匀减速运动,然后使用手指离开时的速度/加速度=总共耗时duration,最后就可以根据上面这些数据计算出每时间内移动的距离,把这个距离当做偏移量去计算我们的数据起始位置,重新绘制即可。
/**
* @param e1 手指按下的位置
* @param e2 手指抬起的位置
* @param velocityX 手指抬起时的x轴的加速度 px/s
*/@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mLastGesture = ChartGesture.FLING;
fling(velocityX, e2.getX() - e1.getX());return true;
}private void fling(float velocity, float offset) {
stopFling();if (Math.abs(mDeceleration) > DataUtils.EPSILON) {// 根据加速度计算速度减少到0时的时间int duration = (int) (1000 * velocity / mDeceleration);// 手指抬起时,缓冲的距离int totalDistance = (int) ((velocity * velocity) / (mDeceleration + mDeceleration));int startX = (int) offset, flingX;if (velocity 0) {
flingX = startX - totalDistance;
} else {
flingX = startX + totalDistance;
}
mFlingRunnable = new FlingRunnable(startX, flingX, duration, mHandler, mChartGestureListener);
mHandler.post(mFlingRunnable);
}
}
放大缩小SCALE
放大缩小的处理稍微就简单了一些,这里监听MotionEvent.ACTION_POINTER_DOWN这个手势,这个手势处理的就是多指按下的情况,根据多指的按下位置和缩放之后的位置计算出一个缩放比出来。然后动态的去更改一屏默认显示的蜡烛线个数,并且更改绘制的起始位置,刷新即可。
case MotionEvent.ACTION_POINTER_DOWN:if (event.getPointerCount() >= 2) {
saveTouchStart(event);// 两个手指之间在X轴的距离
mSavedXDist = getXDist(event);// 两个手指之间的距离
mSavedDist = spacing(event);// 两个手指之间距离大于10才认为是缩放if (mSavedDist > 10f) {
mTouchMode = X_ZOOM;
}// 计算两个手指之间的中点位置
midPoint(mTouchPointCenter, event);
}break;
根据移动后的位置计算缩放比
case MotionEvent.ACTION_MOVE:if (mTouchMode == DRAG) {
mLastGesture = ChartGesture.DRAG;
} else if (mTouchMode == X_ZOOM) {if (event.getPointerCount() >= 2) {// 手指移动的距离float totalDist = spacing(event);if (totalDist > mMinScalePointerDistance) {if (mTouchMode == X_ZOOM) {
mLastGesture = ChartGesture.X_ZOOM;float xDist = getXDist(event);float scaleX = xDist / mSavedXDist;if (mChartGestureListener != null) {
mChartGestureListener.onChartScale(event, scaleX, 1);
}
}
}
}
}
长按LONG_PRESS
长按的处理是简单的,直接实现接口中的onLongPress方法即可知道当前长按的位置。然后根据长按动作去处理十字线以及长按的弹框等
@Overridepublic void onLongPress(MotionEvent e) {
mTouchMode = LONG_PRESS;if (mChartGestureListener != null) {
mChartGestureListener.onChartLongPressed(e);
}
}
数据的处理
使用ChartDataSourceHelper和TechParamsHelper(相关技术指标的计算),根据上面手势移动的偏移量、缩放比进行数据的重组,这块可以直接参考源码阅读即可,没有什么特别复杂的地方。
根据初始位置计算初始化数据
/**
* 初始化行情图初始数据
*/public void initKDrawData(List klineList,
KMasterChartView kLineChartView,
KSubChartView volumeView, KSubChartView macdView) {this.mKList = klineList;this.mKLineChartView = kLineChartView;this.mVolumeView = volumeView;this.mMacdView = macdView;
mSubChartData = new SubChartData();// K线首次当前屏初始位置
startIndex = Math.max(0, klineList.size() - K_D_COLUMNS);// k线首次当前屏结束位置
endIndex = klineList.size() - 1;// 计算技术指标
mTechParamsHelper.caculateTechParams(klineList, TechParamType.BOLL);
mTechParamsHelper.caculateTechParams(klineList, TechParamType.MACD);
initKMoveDrawData(0, SourceType.INIT);
}
当横向滑动、Fling惯性滑动和缩放之后,重新计算初始位置和当前屏幕的蜡烛线等
/**
* 根据移动偏移量计算行情图当前屏数据
*
* @param distance 手指横向移动距离
*/public void initKMoveDrawData(float distance, SourceType sourceType) {// 重置默认值
resetDefaultValue();// 计算当前屏幕开始和结束的位置
countStartEndPos(distance, sourceType);// 计算蜡烛线价格最大最小值,成交量最大值
ExtremeValue extremeValue = countMaxMinValue();// 最大值最小值差值float diffPrice = maxPrice - minPrice;// MACD最大最小值float diffMacd = maxMacd - minMacd;float diffBoll = maxBoll - minBoll;
RectF contentRect = mKLineChartView.getViewPortHandler().mContentRect;// 计算当前屏幕每一个蜡烛线的位置和涨跌情况for (int i = startIndex, k = 0; i KLineItem kLineItem = mKList.get(i);// 开盘价float open = kLineItem.open;// 最低价float close = kLineItem.close;// 最高价float high = kLineItem.high;// 最低价float low = kLineItem.low;
KLineToDrawItem drawItem = new KLineToDrawItem();// 计算蜡烛线float scaleY_open = (maxPrice - open) / diffPrice;float scaleY_low = (maxPrice - close) / diffPrice;
RectF candleRect = getRect(contentRect, k, scaleY_open, scaleY_low);
drawItem.rect = candleRect;// 计算上影线,下影线float scale_HL_T = (maxPrice - high) / diffPrice;float scale_HL_B = (maxPrice - low) / diffPrice;
RectF shadowRect = getLine(contentRect, k, scale_HL_T, scale_HL_B);
drawItem.shadowRect = shadowRect;// 计算红涨绿跌,暂时这么计算(其实红涨绿跌是根据当前开盘价和前一天的收盘价做对比)if (i - 1 >= 0) {
KLineItem preItem = mKList.get(i - 1);if (kLineItem.open > preItem.close) {
drawItem.isFall = false;
} else {
drawItem.isFall = true;
}if (preItem.close != 0) {
kLineItem.preClose = preItem.close;
} else {
kLineItem.preClose = kLineItem.open;
}
}// 计算每一个月的第一个交易日if (i - 1 >= 0 && i + 1 int currentMonth = DateUtils.getMonth(kLineItem.day);
KLineItem preItem = mKList.get(i - 1);int preMonth = DateUtils.getMonth(preItem.day);if (currentMonth != preMonth) {
drawItem.date = kLineItem.day.substring(0, 10);
}
}// 计算成交量if (Math.abs(maxVolume) > DataUtils.EPSILON) {
RectF volumeRct = mVolumeView.getViewPortHandler().mContentRect;float scaleVolume = (maxVolume - kLineItem.volume) / maxVolume;
drawItem.volumeRect = getRect(volumeRct, k, scaleVolume, 1);
}// 计算BOLL
caculateBollPath(diffBoll, contentRect, i, k, drawItem);// 计算附图MACD Path
caculateMacdPath(diffMacd, i, k, drawItem.isFall);
drawItem.klineItem = kLineItem;
kLineItems.add(drawItem);
}
List resultList = new ArrayList<>();// 数据准备完毕if (mReadyListener != null) {
resultList.addAll(kLineItems);
mReadyListener.onReady(resultList, extremeValue, mSubChartData);
}
}
/ 总结 /
目前市面上有很多的自定义图表,但是能将行情图以及各项指标完全复用的基本上没有,比较牛逼的就是MPChart基本上能够满足大部分的图表使用,但是对行情图来说还是远远不够。所以出于兴趣,就模仿火币和炒股软件进行了一个自定义蜡烛线,由于不是专业人士,可能有的金融指标有一些偏差,这里明白绘制技术即可,不必关心这些金融细节。
规划(项目会继续完善更新):
- 后面会继续丰富图标的各项指标
- 数据层要进行整理,目前有些地方处理不是特别高效
- 实现各种图表动态添加、切换等。
github地址:
github.com/SlamDunk007/StockChart
额外福利(名额有限)
小乐个人微信
→ 技术资料共享
→ 技术交流社群
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。
猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
优秀的 Java 项目,代码都是如何分层的?
审阅“史上“最烂的代码
实战:上亿数据如何秒查
关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?
cad渐变线怎么画_花花绿绿的股票线是怎么画出来的?想怎么画就怎么画!相关推荐
- 花花绿绿的股票线是怎么画出来的?想怎么画就怎么画!
/ 今日科技快讯 / 近日,蔚来披露合肥战略投资协议重大进展,已按计划实质性完成注资.今年4月29日,蔚来官方宣布,蔚来中国总部落户合肥项目协议正式签署,战略投资者将向蔚来中国投资70亿元人民 ...
- allegro 走线切换层_高速信号走线的九大规则
规则一:高速信号走线屏蔽规则 www.rubbishsort.com 如上图所示: 在高速的PCB设计中,时钟等关键的高速信号线,走需要进行屏蔽处理,如果没有屏蔽或只屏蔽了部分,都是会造成EMI的泄漏 ...
- WORD如何取消文字下方花花绿绿的波浪线?
是提醒标记,但是提醒是多余的.
- lvds接口屏线安装图解_液晶屏LVDS线类型图文讲解
说明: 1.本资料是部分显示屏所使用的LVDS 线汇总表,其中对LVDS 接口插座.特征.编码等作 了介绍.如果这些显示屏的LVDS 线损坏,可参考: 2.由于1920X1080高清屏对应的数字板LV ...
- 快充线与普通线的区别_四种不同线身材质对比:iPhone12首次标配编织线或将引领潮流?...
不知不觉时间已经来到了八月份,距离苹果秋季新品发布的日期也越来越近了.虽然苹果官方没有任何的消息,但是网上对于新款iPhone 12和其他的硬件产品已经有非常多的讨论了,而据传的不再随包装盒附赠充电头 ...
- 步进电机五根线怎么接_热电阻三根线怎么接 浅谈热电阻识别方法
本文主要是关于热电阻的相关介绍,并着重对热电阻三根线的接法及其识别方法进行了详尽的阐述. 热电阻 热电阻(thermal resistor)是中低温区最常用的一种温度检测器.热电阻测温是基于金属导体的 ...
- vscode波浪线报错_理解vscode波浪线
我打开与ES7属性初始化一个js文件的源和我越来越波浪线:理解vscode波浪线 class AppContainer extends Component { static propTypes = { ...
- 华为荣耀7i刷linux,华为荣耀7i线刷教程_华为荣耀7i线刷包下载_救砖方法
来说说咱们的华为荣耀7i手机的线刷教程了,也就是大家比较关心的救砖教程了,因为手机变砖开不了机了,这个时候没办法往手机里放文件了,也就无法放刷机包了,那这个时候怎么办呢,只能采用线刷的方式来进行救砖了 ...
- lvds接口屏线安装图解_区分LVDS屏线及屏接口定义
1. LVDS屏线按位数主要分为单6位,双6位,单8位,双8位.我们取英文Single(单)和Doubel(双)的首字母分别命名单6位-S6,双6位-D6,单8位-S8,双8位-D8. 2. LVDS ...
最新文章
- KD树是什么? 为什么要用KD树? KD树怎么用? KD树和KNN的关联是什么?
- wPaint在线绘图插件
- Mybatis学习之配置优化
- 【深度学习】3D深度学习简介
- linux的常用操作——read函数和write函数
- WCF Data Services 基础
- java二维数组遍历排序,实现二维数组的按次序排序!!!
- java编程算法出现在窗口_Java实现轨迹压缩算法开放窗口代码编程实例分享
- 卸载WPS后office图标异常解决办法
- win7如何设置电脑自动拨号?
- 木讷的程序员需要知道的事情 (六)
- rf 433/868MHZ sub-1g 无线通信知识系列(3):组网信道
- 拼多多:补贴与盈利背道而驰
- 阿里云 1H2G T5实例 与 腾讯云 1H2G 标准2实例 测试对比
- 学习笔记(97):R语言入门基础-pairs绘图
- 记录vultr搭建https爬虫代理
- “核高基”的专家有哪些人?
- mysql用户的创建、修改、删除与密码修改
- Windows 安全中心空白无选项解决办法
- 苹果手机以旧换新活动_苹果再次清仓iPhone SE,以旧换新活动延期