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

行情K线图也就是我们常说的蜡烛图,是金融类软件里可以说必不可少的,无论日K, 周K,月K,还是分钟K,准确的来表达个股在一定时间内涨跌走势,K线图有着不可无视的作用,其绘制过程也是彰显一个程序员对自定义控件的熟练程度,尤其是对Canvas的灵活运用,绘线,绘边框,及位置的选取,比例的分配,今天这个Demo,则一步步为你诠释。


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




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


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


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


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpecheightMeasureSpec);
    
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(resultspecSize);
    
}
    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(resultspecSize);
    
}
    return result;
}




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


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


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


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


/**
 * 
设置背景色及初始化画笔
 */
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(rmPaint);
}


绘制横线,因为要留出一段区域做刻度绘制,所以,距离左边要有一段距离,这里我设置的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 = 0a < lineXSizea++) {
        float h = height * a + 10f;
        
Log.i("BaseLine"h + "===");
        
canvas.drawLine(100fh, this.getRight() - 10fhmPaint);
    
}
}


绘制纵线,其原理和绘制横线差不多,起始x的位置为距离左边的距离既100,则每份的宽度就是,(当前View的宽度-距离左边的距离-距离右边的距离)/要分为几份,这里我定义的是lineYSize=3:


private int lineYSize 3;

private void drawYLine(Canvas canvas) {
    mPaint.setColor(Color.WHITE);
    float 
width = (this.getRight() - 10f 100f) / lineYSize;
    for 
(int a = 0a < lineYSizea++) {
        float w = width * a + 100f;
        
canvas.drawLine(w10fw, this.getHeight() - 50fmPaint);
    
}
}


绘制底部时间,times是自己定义的一个时间数组,其坐标位置和纵线类似,y值是固定不变的,x轴增加的距离和纵线一致:


private int[] times = {5678};

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


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


private float[] price = {260f240f220f};

private void drawYPrice(Canvas canvas) {
    mPaint.setColor(Color.WHITE);
    float 
height = (this.getHeight() - 10f 50f) / lineXSize;//平均分为几分
    
for (int a = 1a < lineXSizea++) {
        float h = height * a + 10f;
        
canvas.drawText(price[a - 1] + ""40fhmPaint);
    
}
}


经过以上父 类中的绘制,基本的边框,横线,纵线,底部时间,左部价格刻度,就完成了,接下来就是柱状图和十字光标:

自定义一个view集成于父类,实现其构造方法,初始化一些信息,设置画笔为实心的:


private void init() {
    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<StockLineBean> listData new ArrayList<>();

/**
 * 
添加数据
 */
private void setLineData() {
    List<StockLineBean> list = new ArrayList<StockLineBean>();
    
list.add(new StockLineBean(25025124825020170731));
    
list.add(new StockLineBean(24925224825220170730));
    
list.add(new StockLineBean(25025124825020170729));
    
list.add(new StockLineBean(24925224825220170728));
    
list.add(new StockLineBean(24825024725020170727));
    
list.add(new StockLineBean(25625624824820170726));
    
list.add(new StockLineBean(25725825625720170725));
    
list.add(new StockLineBean(25926025625620170724));
    
list.add(new StockLineBean(26126125725920170723));
    
list.add(new StockLineBean(25926025625620170722));
    
list.add(new StockLineBean(26126125725920170721));
    
list.add(new StockLineBean(26026025925920170720));
    
list.add(new StockLineBean(26226226026120170719));
    
list.add(new StockLineBean(26026225926220170718));
    
list.add(new StockLineBean(25926125826120170717));
    
list.add(new StockLineBean(25525925525920170716));
    
list.add(new StockLineBean(25926125826120170715));
    
list.add(new StockLineBean(25525925525920170714));
    
list.add(new StockLineBean(25825825525520170713));
    
list.add(new StockLineBean(25826025826020170712));
    
list.add(new StockLineBean(25926025825920170711));
    
list.add(new StockLineBean(26126225925920170710));
    
list.add(new StockLineBean(26126125826120170709));
    
list.add(new StockLineBean(26126225925920170708));
    
list.add(new StockLineBean(26126125826120170707));
    
list.add(new StockLineBean(26126125926120170706));
    
list.add(new StockLineBean(25726125726120170705));
    
list.add(new StockLineBean(25625725525520170704));
    
list.add(new StockLineBean(25726125726120170703));
    
list.add(new StockLineBean(25625725525520170702));
    
list.add(new StockLineBean(25325725325620170701));
    
list.add(new StockLineBean(25525525225220170630));
    
list.add(new StockLineBean(25625625325520170629));
    
list.add(new StockLineBean(25425625425520170628));
    
list.add(new StockLineBean(24725624725420170627));
    
list.add(new StockLineBean(24424924324820170626));
    
list.add(new StockLineBean(24424524324420170625));
    
list.add(new StockLineBean(24424924324820170624));
    
list.add(new StockLineBean(24424524324420170623));
    
list.add(new StockLineBean(24224424124420170622));
    
list.add(new StockLineBean(24324324124220170621));
    
list.add(new StockLineBean(24624724424420170620));
    
list.add(new StockLineBean(24824924624620170619));
    
list.add(new StockLineBean(25125325025020170618));
    
list.add(new StockLineBean(24824924624620170617));
    
list.add(new StockLineBean(25125325025020170616));
    
list.add(new StockLineBean(24925324925320170615));
    
list.add(new StockLineBean(24825024625020170614));
    
list.add(new StockLineBean(24925024725020170613));
    
list.add(new StockLineBean(25425425025020170612));
    
list.add(new StockLineBean(25425525125520170611));
    
list.add(new StockLineBean(25425425025020170610));
    
list.add(new StockLineBean(25425525125520170609));
    
list.add(new StockLineBean(25225425125420170608));
    
list.add(new StockLineBean(25025325025220170607));
    
list.add(new StockLineBean(25125224725020170606));
    
list.add(new StockLineBean(25325425225420170605));
    
list.add(new StockLineBean(25025425025420170604));
    
list.add(new StockLineBean(25125224725020170603));
    
list.add(new StockLineBean(25325425225420170602));
    
list.add(new StockLineBean(25025425025420170601));
    
list.add(new StockLineBean(25025224825020170531));
    
list.add(new StockLineBean(25325425025120170530));
    
list.add(new StockLineBean(25525625325320170529));
    
list.add(new StockLineBean(25625725325420170528));
    
list.add(new StockLineBean(25525625325320170527));
    
list.add(new StockLineBean(25625725325420170526));
    
list.add(new StockLineBean(25625725425620170525));
    
list.add(new StockLineBean(26526525725720170524));
    
list.add(new StockLineBean(26526626526520170523));
    
list.add(new StockLineBean(26726826526620170522));
    
list.add(new StockLineBean(26426726426720170521));
    
list.add(new StockLineBean(26726826526620170520));
    
list.add(new StockLineBean(26426726426720170519));
    
list.add(new StockLineBean(26426626226520170518));
    
list.add(new StockLineBean(26626726426420170517));
    
list.add(new StockLineBean(26426726326720170516));
    
list.add(new StockLineBean(26626726426420170515));
    
list.add(new StockLineBean(26926926626820170514));
    
list.add(new StockLineBean(26626726426420170513));
    
list.add(new StockLineBean(26926926626820170512));
    
list.add(new StockLineBean(26726926626920170511));
    
list.add(new StockLineBean(26626826626720170510));
    
list.add(new StockLineBean(26426826326620170509));
    
list.add(new StockLineBean(26527126726720170508));
    
list.add(new StockLineBean(26526926526720170507));
    
list.add(new StockLineBean(26526826526720170506));
    
list.add(new StockLineBean(27127126626620170505));
    
list.add(new StockLineBean(27127326927320170504));
    
list.add(new StockLineBean(26827126827120170503));
    
list.add(new StockLineBean(26827026627120170502));
    
list.add(new StockLineBean(26826826327120170501));
    for 
(int a = 0a < list.size()a++) {
        listData.add(0list.get(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 = 0a < listData.size()a++) {
        StockLineBean bean = listData.get(a);
        float 
high = bean.getHigh() - 201.6f;
        float 
low = bean.getLow() - 201.6f;
        float 
left = 100f + xSize * a;
        float 
o = bean.getOpen();
        float 
y = bean.getClose();
        if 
(y > o) {
            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(lefttopleft + xSizebottommPaint);
        
//绘制中间线
        
canvas.drawLine(left + xSize / 2top - 10fleft + xSize / 2bottom + 10fmPaint);
    
}
}


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


private float xMoveyMove;

@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 100f || yMove this.getBottom() - 50f) {
        mPaint.reset();
    
else {
        canvas.drawLine(100fyMove, this.getRight() - 10yMovemPaint);
        
canvas.drawLine(xMove10fxMove, this.getBottom() - 50fmPaint);
        float 
xWidth = ((this.getHeight() - 50f) - yMove) / ySize + 200f;
        
String xContent = String.format("%.0f"xWidth);

        
canvas.drawText(xContent75fyMove 3fmPaint);

        float 
xSize = (this.getRight() - 125f) / 92;
        float 
timeSize = (xMove / xSize) - xSize;
        int 
size = (int) timeSize;
        if 
(size < listData.size()) {
            canvas.drawText(listData.get(size).getDate() + ""xMove 20f, this.getHeight() - 35fmPaint);
        
}
    }
}


通过以上的代码我们就可以绘制出文章开头的效果了,具体使用,只需要在用到的Xml里引用就OK了:


<com.ming.abner.stockline.StockLineK
    android:layout_width="match_parent"
    
android:layout_height="160dp" />

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员一鸣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值