*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

金融类自定义View(二)–股票分时图

本节主要介绍股票分时图的详细实现思路与遇到的各种问题。该分时图主要包括以下功能:数据的适配、x/y背景轴的绘制、x/轴文字的绘制、长按十字的处理、实时折线的处理、分时折线的左右滑动等。主要会陈述的问题:如何实现最新数据横线的实现、实时数据导致折线的变化的实现思路、折线图左右滑动的处理。(参考阅读:百度百科–分时图)

前言

  • 有阅读过第一篇文章的读者可能会说,这特么不和第一篇那个一样嘛。确实,实现思路基本一致,但是复杂度远大于【基金收益自定义view】,包括交互处理与数据处理。
  • 该【股票分时图】当时绘制的时候也没好的分时图进行模仿,就直接拿自己公司同事之前实现的分时图进行了模仿。公司分时图实现是使用了知名的第三方图形库MPAndroidCharthttps://github.com/PhilJay/MPAndroidChart。
  • 既然是进行模仿,基本会严格按照原分时图进行实现,读者可以下载原APP对比体验。
  • 有些地方实现并没有严格按照自定义view的套路去实现,比如自定义View属性的抽取、颜色的抽取等(因为真的属性太多了…),后期会逐步完善抽取。
  • 代码我是尽可能的清晰表达,保证在修改使用的时候可以容易阅读。
  • 本来打算用半年的时候,绘制出自己的金融交易类“K线图”。现在看来,用不了那么久了。因为分时图绘制出来之后,蜡烛图实现就比较简单了–只要在目标点绘制矩形图即可。
  • 代码有点多,enjoy it~

效果图

原【天厚实盘】分时图
  • 【天厚实盘】分时图_默认

  • 【天厚实盘】分时图_长按

  • 【天厚实盘】分时图_gif

仿分时图【TimeSharingView.java】
  • 第一阶段,参数准备,外边框、内虚线、折线图等的绘制

  • 第二阶段,x、y文字、实时横线和实时数据、下方透明阴影

  • 第三阶段,实时数据更新分时图

  • 第四阶段,长按十字线,左右移动分时图

主要运用什么知识

  • 自定义View基础知识
  • 运用Paint绘制文字、绘制横线、绘制矩形
  • 运用Path绘制折线、绘制背景
  • onTouchEvent(MotionEvent event)各种手势的监听与处理
  • 【重点】大量的计算、测量以及定位
  • 线程的切换处理(Rxjava,可选)

参考

  • 加载中文字处理、x/y轴曲线的绘制、x轴时间文字和y轴收益文字的定位和绘制、折线的绘制
    、长按十字的处理请参考第一篇文章:金融类自定义View(一)–仿蚂蚁财富基金收益曲线图

数据量和位置的确认

  • 对于分时图有一个问题,就是数据的加载和实时推送。因为,对于分时图第一次加载的时候,数据量可能是不确定的,可能第一次拉去过来500条,也可能是100条。同时,分时图中显示的条数也是可变的。比如,分时图中可以显示40条,也可以显示100条。对于自定义分时图来说,两边的数据都不确认。这里的处理思路是,定义全局的数据起始点和结束点,每次刷新页面都会重新计算数据的起始位置和结束位置,同时刚加载的时候和滑动的时候数据的起始位置和结束位置计算方式是不一样的。我们多思考一点,其实所谓的K线图的移动,就是重新寻找数据的起始位置和结束位置,计算好新的开始和结束位置,可以刷新界面了,完成移动操作。

      /*** 来最新数据或者刚加载的时候,计算开始位置和结束位置。特别注意,最新的数据在最后面,所以数据范围应该是[(size-mShownMaxCount)~size)*/private void counterBeginAndEndByNewer() {int size = mQuotesList.size();if (size >= mShownMaxCount) {mBeginIndex = size - mShownMaxCount;mEndIndex = mBeginIndex + mShownMaxCount;} else {mBeginIndex = 0;mEndIndex = mBeginIndex + mQuotesList.size();}}/*** 移动K线图计算移动的单位和重新计算起始位置和结束位置* @param moveLen*/private void moveKView(float moveLen) {mPullRight = moveLen > 0;mPullType = moveLen > 0 ? PullType.PULL_RIGHT : PullType.PULL_LEFT;int moveCount = (int) Math.ceil(Math.abs(moveLen) / mPerX);if (mPullRight) {int len = mBeginIndex - moveCount;if (len < 0) {mBeginIndex = 0;} else {mBeginIndex = len;}} else {int len = mBeginIndex + moveCount;if (len + mShownMaxCount > mQuotesList.size()) {mBeginIndex = mQuotesList.size() - mShownMaxCount;} else {mBeginIndex = len;mPullType = PullType.PULL_NONE;//到最左边啦}}mEndIndex = mBeginIndex + mShownMaxCount;//开始位置和结束位置确认好,就可以重绘啦~Log.e(TAG, "moveKView: mPullRight:" + mPullRight + ",mBeginIndex:" + mBeginIndex + ",mEndIndex:" + mEndIndex);processData();}
    

分时图的实时变化

  • 所谓分时图,就是要根据服务推送过来的数据,实时更新折线图。这里的实现采用随机时间模拟服务端推送过来的数据。所谓的折线图实时变化,实现思路其实关键点还是在于数据集合的起始位置和结束位置的确认。确认好起始和结束位置后,重新绘制新的折线即可。因此,该分时图View提供了一个添加单个数据的方法:

     /*** 实时推送过来的数据,实时更新** @param quotes*/public void addTimeSharingData(Quotes quotes) {if (quotes == null) {Toast.makeText(mContext, "数据异常", Toast.LENGTH_SHORT).show();Log.e(TAG, "setTimeSharingData: 数据异常");return;}mQuotesList.add(quotes);//如果实在左右移动,则不去实时更新K线图,但是要把数据加进去if (mPullType == PullType.PULL_NONE) {Log.e(TAG, "addTimeSharingData: 处理实时更新操作...");counterBeginAndEndByNewer();processData();}}
    

折线图的绘制和折线图阴影的处理

  • 由上面两点可以看出,只要有起始位置和结束位置,不仅可以绘制好折线还可以实时更新,那么折线图的绘制就要求很高了。同时,我们可以注意一下,折线图下面的阴影,这个阴影怎么处理?查找了很多资料,并没有Path绘制折线并且绘制不同颜色的背景的处理;并且,折线并不是完整的闭环,而是只有上方一部分。这个地方思考了很久,最终的解决方案是,采用两个Path,第一个Path只绘制用户看到的折线图,同时设置画笔属性mBrokenLinePaint.setStyle(Paint.Style.STROKE);,只绘制折线,不填充内容。另外提供一个Path,只绘制内容,设置画笔属性:mBrokenLineBgPaint.setStyle(Paint.Style.FILL);,另外提供透明效果:mBrokenLineBgPaint.setAlpha(mAlpha);,这样就实现了这种效果:

    //折线图绘制核心代码private void drawBrokenLine(Canvas canvas) {//先画第一个点Quotes quotes = mQuotesList.get(mBeginIndex);Path path = new Path();Path path2 = new Path();//这里需要说明一下,x轴的起始点,其实需要加上mPerX,但是加上之后不是从起始位置开始,不好看。// 同理,for循环内x轴其实需要(i+1)。现在这样处理,最后会留一点空隙,其实挺好看的。float floatY = (float) (mHeight - mPaddingBottom - mInnerBottomBlankPadding - mPerY * (quotes.c - mMinQuotes.c));//在自定义view:FundView中的位置坐标//记录下位置信息quotes.floatX = mPaddingLeft;quotes.floatY = floatY;path.moveTo(mPaddingLeft, floatY);path2.moveTo(mPaddingLeft, floatY);for (int i = mBeginIndex + 1; i < mEndIndex; i++) {Quotes q = mQuotesList.get(i);float floatX2 = mPaddingLeft + mPerX * (i - mBeginIndex);//注意这个 mPerX * (i-mBeginIndex),而不是mPerX * (i)float floatY2 = (float) (mHeight - mPaddingBottom - mInnerBottomBlankPadding - mPerY * (q.c - mMinQuotes.c));//记录下位置信息q.floatX = floatX2;q.floatY = floatY2;path.lineTo(floatX2, floatY2);path2.lineTo(floatX2, floatY2);//最后一个点,画一个小圆点;实时横线;横线的右侧数据与背景;折线下方阴影if (i == mEndIndex - 1) {//绘制小圆点canvas.drawCircle(floatX2, floatY2, mDotRadius, mDotPaint);//接着画实时横线canvas.drawLine(mPaddingLeft, floatY2, mWidth - mPaddingRight, floatY2, mTimingLinePaint);//接着绘制实时横线的右侧数据与背景//文字高度float txtHight = getFontHeight(mTimingTxtWidth, mTimingTxtBgPaint);//绘制背景canvas.drawRect(mWidth - mPaddingRight, floatY2 - txtHight / 2, mWidth, floatY2 + txtHight / 2, mTimingTxtBgPaint);//绘制实时数据//距离左边的距离float leftDis = 8;canvas.drawText(FormatUtil.numFormat(q.c, mDigits), mWidth - mPaddingRight + leftDis, floatY2 + txtHight / 4, mTimingTxtPaint);//在这里把path圈起来,添加阴影。特别注意,这里处理下方阴影和折线边框。采用两个画笔和两个Path处理的,貌似没有一个Paint可以同时绘制边框和填充色path2.lineTo(floatX2, mHeight - mPaddingBottom);path2.lineTo(mPaddingLeft, mHeight - mPaddingBottom);path2.close();}}canvas.drawPath(path, mBrokenLinePaint);canvas.drawPath(path2, mBrokenLineBgPaint);}

折线图的左右滑动处理

  • 这个功能的实现考虑了很久,不知道怎么实现。当时还在考虑安卓的画布是否会提供类似于ScrollView的功能,可以绘制很多View,然后可以左右滑动View;或者类似于ListView….(并没有)
  • 最终的实现思路是这样的(有线上运行的分时图是这样处理的,可以放心使用!):

    1. 监听滑动
    2. 手指滑动的时候,记录下滑动的x轴的距离
    3. 根据单位数据的x轴的距离,计算出需要移动几个单位的数据(注意边界和方向问题)
    4. 根据移动的单位,重新计算起始位置和结束位置(其实最上面的起始位置和结束位置的确认,是在这里得到启发的)
    5. 刷新View

          //监听判断手指移动@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mPressedX = event.getX();mPressTime = event.getDownTime();//按下的手指个数mFingerPressedCount = event.getPointerCount();break;case MotionEvent.ACTION_MOVE:...//判断是否是手指移动float currentPressedX = event.getX();float moveLen = currentPressedX - mPressedX;//重置当前按下的位置【不要忘了】mPressedX = currentPressedX;if (Math.abs(moveLen) > DEF_PULL_LENGTH && mFingerPressedCount == 1) {Log.e(TAG, "onTouchEvent: 正在移动分时图");//移动k线图moveKView(moveLen);}break;...}/*** 移动K线图计算移动的单位和重新计算起始位置和结束位置* @param moveLen*/private void moveKView(float moveLen) {mPullRight = moveLen > 0;mPullType = moveLen > 0 ? PullType.PULL_RIGHT : PullType.PULL_LEFT;int moveCount = (int) Math.ceil(Math.abs(moveLen) / mPerX);if (mPullRight) {int len = mBeginIndex - moveCount;if (len < 0) {mBeginIndex = 0;} else {mBeginIndex = len;}} else {int len = mBeginIndex + moveCount;if (len + mShownMaxCount > mQuotesList.size()) {mBeginIndex = mQuotesList.size() - mShownMaxCount;} else {mBeginIndex = len;mPullType = PullType.PULL_NONE;//到最左边啦}}mEndIndex = mBeginIndex + mShownMaxCount;//开始位置和结束位置确认好,就可以重绘啦~Log.e(TAG, "moveKView: mPullRight:" + mPullRight + ",mBeginIndex:" + mBeginIndex + ",mEndIndex:" + mEndIndex);//这里就是处理和刷新数据processData();}
      

待处理的问题

  • 长按十字,上方应该显示:开盘价、收盘价、最高价、最低价、时间等信息;这里不再将这一块在View中绘制,而是在xml中进行绘制即可。该View其实很简单,只需要提供一个接口,把点击长按的数据回调出去就好~
  • 双指View缩放的处理,这里当时考虑很难处理的。但是,现在已经有思路了:改变显示的View的数据量,同时找到中间位置,以该位置左右加减指定的数据量,刷新界面即可!
  • 滑动到最左边,加载更多数据….
  • 手指滑动、更新数据都会导致整个View的重新绘制,可能短时间内会导致N次重绘,感觉严重影响性能。但是,问了一些朋友,貌似都是这样处理的。。。没有其它方式吗?
  • 打个广告,公司一直在招聘,java、DBA、H5、Devlops等,坐标:上海,欢迎来投:天厚投资 Tophold

code

  • https://github.com/scsfwgy/FinancialCustomerView
  • 注:该项目会一直维护,不断加入新的关于金融类的各种自定义View。最终的目标是绘制出复杂多变的K线图~

金融类自定义View(二)--股票分时图相关推荐

  1. 金融类自定义View(四)--股票蜡烛图以及MA、BOLL指标

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 金融类自定义View(四)–股票蜡烛图(包含MA.BOLL指标)以及代码重构 前言 本文只描述蜡烛图单独的绘制逻辑,至于和分时图相 ...

  2. python股票交易接口实现股票分时图K线图及抓取level2行情的开发程序分析

    大家对股票交易接口并不陌生,那么要想获取股票数据获取,也是可以通过python股票交易接口实现股票分时图K线图及抓取level2行情的开发程序分析如下: import sys from PyQt5 i ...

  3. android下雨动画效果,Android 自定义View(二) 下雨效果

    Rain.gif Android 自定义View(二) 下雨效果 一 实现思路, 雨点用线段表示,通过控制线段的大小和宽度来表示不同的线段. 一个雨点下雨的过程可以表示为一条直线,一次雨点在下雨的过程 ...

  4. 用python画股票分时图 github_用python的matplotlib和numpy库绘制股票K线均线和成交量的整合效果(含量化验证交易策略代码)...

    在用python的matplotlib和numpy库绘制股票K线均线的整合效果(含从网络接口爬取数据和验证交易策略代码)一文里,我讲述了通过爬虫接口得到股票数据并绘制出K线均线图形的方式,在本文里,将 ...

  5. Android 自定义View -- 简约的折线图

    转载请注明出处:http://write.blog.csdn.net/postedit/50434634 接上篇 Android 圆形百分比(进度条) 自定义view 昨天分手了,不开心,来练练自定义 ...

  6. 自定义View(二),强大的Canvas

    本文转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android中使用图形处理引擎,2D部分是 ...

  7. Android自定义View(二)

    文章目录 1.构造函数 2.onMeasure() 2.1.MeasureSpec 3.onSizeChanged() 4.onLayout() 5.onDraw() 上一篇: Android自定义V ...

  8. TUSHARE绘制股票分时图

    1.注册网站https://tushare.pro/,获取积分 2. 点右上角的登陆,点击个人头像,复制token 3.调取模块,并加载你的token import tushare as ts pro ...

  9. 自定义view 自动轮播图+GridView

    MainActivity 主方法 package com.bawie.mymonizhoukao1;import android.support.v7.app.AppCompatActivity; i ...

  10. Flutter 自定义View之 饼状图

    版权声明:本文为博主原创文章,转载请注明出处! 今天跟大家分享的是用Flutter来实现的自定义饼状图,下面来看看效果! 通过点击左右两侧的按钮,可以实现扇形切换,被选中的扇形有个放大的效果,中间的百 ...

最新文章

  1. ❤️手撕这十道HiveSQL题还不能吊打面试官,却能保你不被吊打❤️【推荐收藏】
  2. java8新特性(四)_Stream详解
  3. ruby和python简单对比
  4. Linux LVM与磁盘配额详解
  5. 【Linux】kali linux 安装 google chrome
  6. python定时杀进程
  7. 怎样对流媒体进行压力测试_四合一气体检测仪怎样进行气体测试?
  8. python tkinter grid为什么不能用_Python Tkinter教程之Grid篇
  9. 行业认证标准:IEC 61508电气/电子产品功能安全“通用”国际标准
  10. python爬虫设计图片大全_PYTHON爬虫——必应图片关键词爬取
  11. ​成为一家机器学习公司意味着投资基础技术
  12. 《Neural Architectures for Nested NER through Linearization》
  13. 前端开发:Promise的使用丨蓄力计划
  14. 手写迷你Spring框架
  15. 用了这么多年百度搜索, 今天才发现加上双引号搜索结果这么准
  16. 世界时区: UTC/GMT+08:00 东八区
  17. vim常用插件安装及使用
  18. linux查看服务器带宽
  19. python主函数写法
  20. VMware esxcli定时开启虚拟机

热门文章

  1. 服务器动态文档技术——CGI、Servlet技术、动态页面Web
  2. 内存颗粒的逻辑bank理解
  3. linux 内存碎片信息查看
  4. PDF太大怎么办?缩小PDF的4种常用方法
  5. H5制作平台开发游戏成本解析
  6. ps还原上一步快捷键_ps怎么返回上一步?PS返回上一步快捷键
  7. android x86玩和平精英,和平精英iOS和安卓可以一起玩吗 和平精英iOS和安卓数据互通吗...
  8. 迷茫时的3个选择,决定了你人生的高度
  9. 民生银行java 面试_2019民生银行招聘面试试题及答案解析
  10. VUM升级esxi主机6.0至6.7U3失败-无法再主机上执行升级脚本