一步步搞定Android行情K线蜡烛图(带十字光标)

[复制链接]
[投诉]
155 0
online_moderator 爱卡网小编手机认证 实名认证 发表于 2022-6-8 20:45:00 | 只看该作者 |阅读模式 打印 上一主题 下一主题
行情K线图也就是我们常说的蜡烛图,是金融类软件里可以说必不可少的,无论日K, 周K,月K,还是分钟K,准确的来表达个股在一定时间内涨跌走势,K线图有着不可无视的作用,其绘制过程也是彰显一个程序员对自定义控件的熟练程度,尤其是对Canvas的灵活运用,绘线,绘边框,及位置的选取,比例的分配,今天这个Demo,则一步步为你诠释。



按惯例,先看下今天要实现的效果,整个Demo地址为:http://download.csdn.net/detail/ming_147/9732963,也可以关注公众号后(评论区第一条评论扫描即可)回复“行情k线图”,源码就会发送给您,公众号有很多android及其它技术文章,还请大家承蒙关注。




一步步搞定Android行情K线蜡烛图(带十字光标)




相对来说比较简单的一个小Demo,为什么来说简单呢,一数据是固定的,二,时间是固定的,相比较实际项目中来说,这已经相当的简单了,我们可以简单的分一下步骤模块,然后再按照依次来进行实现,通过上面的图片,我们可以大致分为,边框,横线,纵线,底部时间,左边刻度,柱状图(蜡烛图),十字光标这几个部分,好,分好之后,我们就来一步步实现吧。



由于代码稍多,为显得代码结构清晰,我们可以先写一个父类,用于实现边框,横纵线,及底部时间,左部刻度的绘制,柱状图(蜡烛图)及十字光标我们放在子类中实现。



自定义一个父类继承于View,实现其构造方法,在onMeasure方法里设置View的大小:



@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     setMeasuredDimension(measureWidth(widthMeasureSpec),
             measureHeight(heightMeasureSpec));
}



private int measureWidth(int measureSpec) {
     int result = 0;
     int specMode = MeasureSpec.getMode(measureSpec);//得到模式
     int specSize = MeasureSpec.getSize(measureSpec);//得到尺寸
     if (specMode == MeasureSpec.EXACTLY) {
         result = specSize;
     } else if (specMode == MeasureSpec.AT_MOST) {
         result = Math.min(result, specSize);
     }
     return result;
}
private int measureHeight(int measureSpec) {
     int result = 0;
     int specMode = MeasureSpec.getMode(measureSpec);
     int specSize = MeasureSpec.getSize(measureSpec);
     if (specMode == MeasureSpec.EXACTLY) {
         result = specSize;
     } else if (specMode == MeasureSpec.AT_MOST) {
         result = Math.min(result, specSize);
     }
     return result;
}







这里简单对两个类型做下解释:



MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如:andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。


MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。



设置完大小之后,我们先在构造方法里初始化一些信息,比如背景色,画笔:



/**
  * 设置背景色及初始化画笔
  */
private void init() {
     setBackgroundColor(Color.parseColor("#222222"));
     mPaint = new Paint();
     mPaint.setStrokeWidth(1);
     mPaint.setStyle(Paint.Style.STROKE);
}



重写onDraw方法,并在方法内绘制相关信息。绘制边框,距离左上各位10,距离右边为View宽度-10,距离底部为View高度-50:



private void drawBorder(Canvas canvas) {
     mPaint.setColor(Color.WHITE);
     Rect r = new Rect();
     r.left = 10;
     r.top = 10;
     r.right = this.getRight() - 10;
     r.bottom = this.getHeight() - 50;
     canvas.drawRect(r, mPaint);
}



绘制横线,因为要留出一段区域做刻度绘制,所以,距离左边要有一段距离,这里我设置的100,所以每条横线的起始位置一定,都是100,因为边框的最右边为View宽度-10,所以横线的终止位置也是一致,起始y的位置和终止y的位置应当一致,按照一定的距离等分开来,这里的lineSize是要分成几份,我定义的是4份,则每份的长度就为:(当前View的高度-距离底部的距离-距离上部的距离)/lineXSize:



private int lineXSize = 4;

private void drawXLine(Canvas canvas) {
     mPaint.setColor(Color.WHITE);
     float height = (this.getHeight() - 10f - 50f) / lineXSize;//平均分为几分
     for (int a = 0; a
     mPaint.setColor(Color.WHITE);
     float width = (this.getRight() - 10f - 100f) / lineYSize;
     for (int a = 0; a 5, 6, 7, 8};

private void drawTimes(Canvas canvas) {
     mPaint.setColor(Color.parseColor("#FF00FF"));
     mPaint.setTextSize(24);
     float width = (this.getRight() - 10f - 100f) / lineYSize;
     for (int a = 0; a
             canvas.drawText(times[a] + "月", w - 30f, this.getHeight() - 25f, mPaint);
         } else {
             canvas.drawText(times[a] + "月", w - 15f, this.getHeight() - 25f, mPaint);
         }
     }
}



绘制Y轴价格刻度,价格刻度的绘制,就和绘制横线有点类似了,price是自己定义的一个刻度数组:



private float[] price = {260f, 240f, 220f};

private void drawYPrice(Canvas canvas) {
     mPaint.setColor(Color.WHITE);
     float height = (this.getHeight() - 10f - 50f) / lineXSize;//平均分为几分
     for (int a = 1; a
     mPaint = new Paint();
     mPaint.setStrokeWidth(1);
     mPaint.setStyle(Paint.Style.FILL);
}



绘制蜡烛图之前,我们需要初始化一些我们需要的数据,这里我定义了一个javaBean,里面我定义了一些数据,开盘,收盘,最高,最低,日期,实现其构造方法和get,set方法。



/**
  * 开盘价
  */
private float open;
/**
  * 最高价
  */
private float high;
/**
  * 最低价
  */
private float low;
/**
  * 收盘价
  */
private float close;
/**
  * 日期
  */
private int date;



javaBean实现之后,我们就可以添加模拟数据了,毕竟不是真实的项目中,所以数据,只能自己去创造了,listData是自己定义存储数据的:



protected List listData = new ArrayList();

/**
  * 添加数据
  */
private void setLineData() {
     List list = new ArrayList();
     list.add(new StockLineBean(250, 251, 248, 250, 20170731));
     list.add(new StockLineBean(249, 252, 248, 252, 20170730));
     list.add(new StockLineBean(250, 251, 248, 250, 20170729));
     list.add(new StockLineBean(249, 252, 248, 252, 20170728));
     list.add(new StockLineBean(248, 250, 247, 250, 20170727));
     list.add(new StockLineBean(256, 256, 248, 248, 20170726));
     list.add(new StockLineBean(257, 258, 256, 257, 20170725));
     list.add(new StockLineBean(259, 260, 256, 256, 20170724));
     list.add(new StockLineBean(261, 261, 257, 259, 20170723));
     list.add(new StockLineBean(259, 260, 256, 256, 20170722));
     list.add(new StockLineBean(261, 261, 257, 259, 20170721));
     list.add(new StockLineBean(260, 260, 259, 259, 20170720));
     list.add(new StockLineBean(262, 262, 260, 261, 20170719));
     list.add(new StockLineBean(260, 262, 259, 262, 20170718));
     list.add(new StockLineBean(259, 261, 258, 261, 20170717));
     list.add(new StockLineBean(255, 259, 255, 259, 20170716));
     list.add(new StockLineBean(259, 261, 258, 261, 20170715));
     list.add(new StockLineBean(255, 259, 255, 259, 20170714));
     list.add(new StockLineBean(258, 258, 255, 255, 20170713));
     list.add(new StockLineBean(258, 260, 258, 260, 20170712));
     list.add(new StockLineBean(259, 260, 258, 259, 20170711));
     list.add(new StockLineBean(261, 262, 259, 259, 20170710));
     list.add(new StockLineBean(261, 261, 258, 261, 20170709));
     list.add(new StockLineBean(261, 262, 259, 259, 20170708));
     list.add(new StockLineBean(261, 261, 258, 261, 20170707));
     list.add(new StockLineBean(261, 261, 259, 261, 20170706));
     list.add(new StockLineBean(257, 261, 257, 261, 20170705));
     list.add(new StockLineBean(256, 257, 255, 255, 20170704));
     list.add(new StockLineBean(257, 261, 257, 261, 20170703));
     list.add(new StockLineBean(256, 257, 255, 255, 20170702));
     list.add(new StockLineBean(253, 257, 253, 256, 20170701));
     list.add(new StockLineBean(255, 255, 252, 252, 20170630));
     list.add(new StockLineBean(256, 256, 253, 255, 20170629));
     list.add(new StockLineBean(254, 256, 254, 255, 20170628));
     list.add(new StockLineBean(247, 256, 247, 254, 20170627));
     list.add(new StockLineBean(244, 249, 243, 248, 20170626));
     list.add(new StockLineBean(244, 245, 243, 244, 20170625));
     list.add(new StockLineBean(244, 249, 243, 248, 20170624));
     list.add(new StockLineBean(244, 245, 243, 244, 20170623));
     list.add(new StockLineBean(242, 244, 241, 244, 20170622));
     list.add(new StockLineBean(243, 243, 241, 242, 20170621));
     list.add(new StockLineBean(246, 247, 244, 244, 20170620));
     list.add(new StockLineBean(248, 249, 246, 246, 20170619));
     list.add(new StockLineBean(251, 253, 250, 250, 20170618));
     list.add(new StockLineBean(248, 249, 246, 246, 20170617));
     list.add(new StockLineBean(251, 253, 250, 250, 20170616));
     list.add(new StockLineBean(249, 253, 249, 253, 20170615));
     list.add(new StockLineBean(248, 250, 246, 250, 20170614));
     list.add(new StockLineBean(249, 250, 247, 250, 20170613));
     list.add(new StockLineBean(254, 254, 250, 250, 20170612));
     list.add(new StockLineBean(254, 255, 251, 255, 20170611));
     list.add(new StockLineBean(254, 254, 250, 250, 20170610));
     list.add(new StockLineBean(254, 255, 251, 255, 20170609));
     list.add(new StockLineBean(252, 254, 251, 254, 20170608));
     list.add(new StockLineBean(250, 253, 250, 252, 20170607));
     list.add(new StockLineBean(251, 252, 247, 250, 20170606));
     list.add(new StockLineBean(253, 254, 252, 254, 20170605));
     list.add(new StockLineBean(250, 254, 250, 254, 20170604));
     list.add(new StockLineBean(251, 252, 247, 250, 20170603));
     list.add(new StockLineBean(253, 254, 252, 254, 20170602));
     list.add(new StockLineBean(250, 254, 250, 254, 20170601));
     list.add(new StockLineBean(250, 252, 248, 250, 20170531));
     list.add(new StockLineBean(253, 254, 250, 251, 20170530));
     list.add(new StockLineBean(255, 256, 253, 253, 20170529));
     list.add(new StockLineBean(256, 257, 253, 254, 20170528));
     list.add(new StockLineBean(255, 256, 253, 253, 20170527));
     list.add(new StockLineBean(256, 257, 253, 254, 20170526));
     list.add(new StockLineBean(256, 257, 254, 256, 20170525));
     list.add(new StockLineBean(265, 265, 257, 257, 20170524));
     list.add(new StockLineBean(265, 266, 265, 265, 20170523));
     list.add(new StockLineBean(267, 268, 265, 266, 20170522));
     list.add(new StockLineBean(264, 267, 264, 267, 20170521));
     list.add(new StockLineBean(267, 268, 265, 266, 20170520));
     list.add(new StockLineBean(264, 267, 264, 267, 20170519));
     list.add(new StockLineBean(264, 266, 262, 265, 20170518));
     list.add(new StockLineBean(266, 267, 264, 264, 20170517));
     list.add(new StockLineBean(264, 267, 263, 267, 20170516));
     list.add(new StockLineBean(266, 267, 264, 264, 20170515));
     list.add(new StockLineBean(269, 269, 266, 268, 20170514));
     list.add(new StockLineBean(266, 267, 264, 264, 20170513));
     list.add(new StockLineBean(269, 269, 266, 268, 20170512));
     list.add(new StockLineBean(267, 269, 266, 269, 20170511));
     list.add(new StockLineBean(266, 268, 266, 267, 20170510));
     list.add(new StockLineBean(264, 268, 263, 266, 20170509));
     list.add(new StockLineBean(265, 271, 267, 267, 20170508));
     list.add(new StockLineBean(265, 269, 265, 267, 20170507));
     list.add(new StockLineBean(265, 268, 265, 267, 20170506));
     list.add(new StockLineBean(271, 271, 266, 266, 20170505));
     list.add(new StockLineBean(271, 273, 269, 273, 20170504));
     list.add(new StockLineBean(268, 271, 268, 271, 20170503));
     list.add(new StockLineBean(268, 270, 266, 271, 20170502));
     list.add(new StockLineBean(268, 268, 263, 271, 20170501));
     for (int a = 0; a



有了数据,我们就可以绘制蜡烛图了,如果开盘大于昨收,蜡烛图就为红色,否则就为绿色,因为,纵轴起始位置是从200开始算的,所以我们取得的最大与最小值再计算的时候,要减去其起始位置,和线的宽度;



每个蜡烛图的高就为:(当前View的高度-距离上下的距离)-当前位置最高值*(当前View的高度-距离上下的距离)/要分的份数(这里是200到280共80份);



每个蜡烛图的低就为:(当前View的高度-距离上下的距离)-当前位置最低值 *(当前View的宽度-距离左右的距离)/要分的份数(这里是3个月共92天);



每个蜡烛图的左边就是:(当前View的宽度-距离左右的距离)/要分的份数(这里是3个月共92天)+当前View距离左边的距离*第几个蜡烛图;



每个蜡烛图的右边就是:蜡烛图的左边+(当前View的宽度-距离左右的距离)/要分的份数(这里是3个月共92天);



绘制蜡烛图的中间线,x 轴的起始位置都是:蜡烛图的左边+(当前View的宽度-距离左右的距离)/要分的份数(这里是3个月共92天)/2,y轴的起始位置为:每个蜡烛图的高-10,y轴的终止位置为:每个蜡烛图的低+10;



private void drawCandleSticks(Canvas canvas) {
     float ySize = (this.getHeight() - 60f) / 80f;
     float xSize = (this.getRight() - 110f) / 92;
     for (int a = 0; a
             mPaint.setColor(Color.RED);
         } else {
             mPaint.setColor(Color.GREEN);
         }
         float top = (this.getHeight() - 60f) - high * ySize;
         float bottom = (this.getHeight() - 60f) - low * ySize;
         canvas.drawRect(left, top, left + xSize, bottom, mPaint);
         //绘制中间线
         canvas.drawLine(left + xSize / 2, top - 10f, left + xSize / 2, bottom + 10f, mPaint);
     }
}



绘制十字光标,就需要重写onTouchEvent方法:



private float xMove, yMove;
@Override
public boolean onTouchEvent(MotionEvent event) {
     super.onTouchEvent(event);
     switch (event.getAction()) {
         case MotionEvent.ACTION_MOVE:
             xMove = event.getX();
             yMove = event.getY();
             super.invalidate();
             break;
     }
     return true;
}



获取好位置之后,我们就可以在onDraw方法里绘制十字光标了,因为 View距离左边和底部有一定的距离,所以在这距离里,我们可以不设置十字光标,十字光标,两条线,一条横线,一条纵线:



横线:起始x轴的位置为当前View距离左边的距离,终止位置就是当前View宽度-10,起始和终止都是手指移动的y值;



纵线:起始x轴的位置就是手指移动的x坐标,起始y值为当前View距离上边的距离,终止y值就是当前View距离底部的距离;



左边变化刻度值:x值为固定的,我这里给出的是75,y坐标是移动的y值+3,其值的计算是:(当前View的高度-距离上下的距离-手指移动的y坐标)/(当前View的高度-距离上下的距离)/要分的份数(这里是200到280共80份)+初始位置刻度。



底部时间变化,x坐标为手指移动的x值-20,y坐标为当前View的高度-35,尽量在底部线的下面,值的计算是:先得到的索引,然后再从listData集合里取得时间。索引的计算方式为:(当前手指移动的x坐标/(当前 View的宽度-左右的距离及几根线的宽度)/总的天数)-(当前 View的宽度-左右的距离及几根线的宽度)/总的天数;



private void drawWithFingerClick(Canvas canvas) {
     float ySize = (this.getHeight() - 60f) / 80f;
     if (xMove  this.getBottom() - 50f) {
         mPaint.reset();
     } else {
         canvas.drawLine(100f, yMove, this.getRight() - 10, yMove, mPaint);
         canvas.drawLine(xMove, 10f, xMove, this.getBottom() - 50f, mPaint);
         float xWidth = ((this.getHeight() - 50f) - yMove) / ySize + 200f;
         String xContent = String.format("%.0f", xWidth);
         canvas.drawText(xContent, 75f, yMove + 3f, mPaint);
         float xSize = (this.getRight() - 125f) / 92;
         float timeSize = (xMove / xSize) - xSize;
         int size = (int) timeSize;
         if (size

版权声明:本站部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们将及时更正、删除,谢谢!


weinxin
爱卡网站长——亚熙哥
若开通会员无法使用
请扫码联系爱卡网站长
本平台不放贷、也不接网贷代做!
上一篇:股票从入门到放弃 --- K线、蜡烛线(一)
下一篇:修正版K线图
楼主热帖
分享到:  QQ好友和群QQ好友和群
收藏
收藏0
支持
支持0
反对
反对0
122329my40v0m19mm281y0
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 qq_login wechat_login1

本版积分规则

avatar

0关注

26粉丝

1091904帖子

发布主题
精选帖子
热帖排行
推荐
162349x8b848x8bpq4qaaz
客服咨询

155-5555-5876

服务时间 9:00-24:00

爱卡网APP下载

app

知识改变命运,技术改变未来!

  • 客服Q Q:70079566     微信:79710
  • 工作时间:周一到周日     9:00-22:00
  • 加入爱卡网VIP会员,菜鸟也能变大神!
15555555876

关注我们

  • app
  • app
  • app
本站唯一官网:www.7177.cn Copyright    2010-2021  爱卡网  Powered by Discuz!  技术支持:亚熙工作室   浙ICP备17046104号-1