之前自定义View文章写过好几篇了,像折线图的绘制,及行情蜡烛图的绘制,都有写过,但对于分时图的绘制,还没有总结过,正好,最近公司不是很忙,索性就重新的画一下及总结一下,可能绘制的比较简单,但基本上该包含的也都有所涉及,比如触摸十字光标,价格时间的显示都有。
先看一下最终实现的效果吧:
设置数据源:
一般开发当中的数据,无论长连接还是短连接,服务端都会给我们返回过来数据,但这里毕竟是一个Demo,所以数据都是一些自己定义的假数据:这里我定义了一个月的数据量,看上图也会发现,只绘制一个月的,其实时间无论多少,只要掌握了绘制的方式,举一反三,就很容易绘制了。
/*** 模拟时间数据* */privateString[] times={"2018/1/1","2018/1/2","2018/1/3","2018/1/4","2018/1/5","2018/1/6","2018/1/7","2018/1/8","2018/1/9","2018/1/10","2018/1/11","2018/1/12","2018/1/13","2018/1/14","2018/1/15" ,"2018/1/16","2018/1/17","2018/1/18","2018/1/19","2018/1/20","2018/1/21","2018/1/22","2018/1/23","2018/1/24","2018/1/25","2018/1/26","2018/1/27","2018/1/28","2018/1/29","2018/1/30"}; /*** 模拟价格数据* */privateint[] prices={121,125,128,122,150,168,133,166,120,166,188,200,220,215,210,190,180,148,136,158,168,198,120,135,168,133,200,230,200,188};
具体分析:
俗话说磨刀不误砍柴工,要把大象装进冰箱,主要分几步,这里我们也要去分析一下具体实现步骤,毕竟有了清晰的思路,那么后面绘制也就更加的有条不紊了,看看上图,这里我绘制的流程是,第一、先绘制了主要的干线,上下两条实线,中间三条虚线,第二、绘制左边价格和底部时间,第三、绘制价格波动也就是折线图,第四、绘制十字光标和右下价格时间展示。
开始绘制:
具体分析之后,那么就开始我们的绘制吧,首先自定义一个类去继承于View,实现其构造及其他方法,并初始化信息,这里我们主要讲的是绘制过程,其它方法,请看源码。
/*** 绘制折线时的路径* */privatePathmPath; /*** 初始化一些信息* path* 背景等* */privatevoidinitView(Contextcontext) { setBackgroundColor(Color.parseColor("#222222"));//设置背景为黑色mPath=newPath(); }
1、绘制主要干线
两条实线和三条虚线
/*** 初始化边框画笔信息(实线)* */privatePaintmRectPaint; privatevoidinitRectPaint(){ mRectPaint=newPaint(); mRectPaint.setColor(Color.parseColor("#ffffff")); mRectPaint.setAntiAlias(true);//设置抗锯齿mRectPaint.setStrokeWidth(2);//线条粗细mRectPaint.setStyle(Paint.Style.STROKE);//设置空心} /*** 初始化虚线边框画笔信息* */privatePaintmInnerXPaint; privatevoidinitInnerXPaint(){ mInnerXPaint=newPaint(); mInnerXPaint.setColor(Color.parseColor("#ffffff")); mInnerXPaint.setAntiAlias(true);//设置抗锯齿mInnerXPaint.setStrokeWidth(2);//线条粗细mInnerXPaint.setStyle(Paint.Style.STROKE);//设置空心setLayerType(LAYER_TYPE_SOFTWARE, null);//禁用硬件加速PathEffecteffects=newDashPathEffect(newfloat[] {15, 5}, 1); mInnerXPaint.setPathEffect(effects); } /*** 绘制边框 上下两条实线* */privateintrectLeft=50, rectTop=10,rectRight=50,rectBottom=50; privatevoidonDrawRect() { canvas.drawLine(rectLeft,rectTop,mWidth-rectRight,rectTop,mRectPaint); canvas.drawLine(rectLeft,mHeight-rectBottom,mWidth-rectRight,mHeight-rectBottom,mRectPaint); } /*** 绘制虚线,三条* 由于只绘制中间,a=1,减少开始和结束时的重复绘制* */privatevoidonDrawInnerLine() { intheightInner=(mHeight-rectTop-rectBottom)/4; for (inta=1;a<4;a++){ canvas.drawLine(rectLeft,heightInner*a,mWidth-rectRight,heightInner*a,mInnerXPaint); } }
2、绘制左边价格和底部时间
由于都是白色,这里我直接使用的是边框的画笔
/*** 绘制边框左边的价格*返回的数据源,我们这里分成5个区间,根据返回的价格最大减去最小来确定区间的大小* 一般价格都是从下到上增大** */privatevoidonDrawLeftPrice() { intheightInner=(mHeight-rectBottom)/4; for (inta=0;a<priceLeft.length;a++){ floatleftPrice=heightInner*a; if (a==0){ leftPrice=rectTop+5; } canvas.drawText(priceLeft[a],rectLeft-35,leftPrice,mRectPaint); } } /*** 绘制底部时间,我这里只显示了三个* */privatevoidonDrawBootmTime() { floatwidthTime=(mWidth-rectLeft-rectRight)/2; for (inta=0;a<3;a++){ floattime=widthTime*a; canvas.drawText(bootomTime[a],time,mHeight-20,mRectPaint); } }
3、绘制价格波动也就是折线图
这里有一个stockList,是传入的数据集合,StockBean是数据对象,也就是把上边的时间和价格追加到集合里,StockBean有四个参数,除了价格和时间,还有就是价格和时间所对应的XY坐标,绘制折线图的时候,添加对象里,在后续的十字光标有用到,具体可看源码:
/*** 初始化折线图画笔* */privatePaintmBrokenPaint; privatevoidinitBrokenPaint() { mBrokenPaint=newPaint(); mBrokenPaint.setColor(Color.parseColor("#3785d9")); mBrokenPaint.setStyle(Paint.Style.STROKE); mBrokenPaint.setAntiAlias(true); mBrokenPaint.setStrokeWidth(2); } /*** 绘制折线图* */privatevoidonDrawZheLine() { floattimeX= ( mWidth-rectLeft-rectRight)/stockList.size();//一份时间所占的宽度floatpriceY= ( mHeight-rectTop-rectBottom)/(maxStock-minStock);//一份价格所占的高度for (inta=0;a<stockList.size();a++){ StockBeanb=stockList.get(a); floatx=timeX*a; floaty=b.getPrice()-minStock; if(a==0){ mPath.moveTo(rectLeft,mHeight-rectBottom); } Log.i("onDrawZheLine",priceY+"----"+timeX+"-----"+x+"-------"+y); floatxLine=x+rectLeft; floatyLine=mHeight-(rectBottom+y*priceY); b.setStockX(xLine); b.setStockY(yLine); mPath.lineTo(xLine,yLine); } canvas.drawPath(mPath,mBrokenPaint); }
4、绘制十字光标和右下价格时间展示,这里longTime是1000,我判断只要大于1000,我就认为是长按,抬起时过1000就隐藏十字光标
privatelongdownTime;//按下时间privatebooleanisShowShiLine=false;//是否显示十字光标privatefloatmoveX,moveY; publicbooleanonTouchEvent(MotionEventevent) { super.onTouchEvent(event); switch (event.getAction()){ caseMotionEvent.ACTION_DOWN: downTime=event.getDownTime(); break; caseMotionEvent.ACTION_UP: hiddenLongPressView(); break; caseMotionEvent.ACTION_MOVE: longeventTime=event.getEventTime(); if((eventTime-downTime)>longTime){//长按moveX=event.getX(); moveY=event.getY(); isShowShiLine=true; invalidate(); } break; } returntrue; } /*** 抬起后過一秒后隐藏十字光标* */privatevoidhiddenLongPressView() { postDelayed(newRunnable() { publicvoidrun() { isShowShiLine=false; invalidate(); } }, 1000); } /*** 初始化十字光标* */privatePaintmShiPaint; privatevoidinitShiLine() { mShiPaint=newPaint(); mShiPaint.setColor(Color.parseColor("#d43c3c")); mShiPaint.setStyle(Paint.Style.STROKE); mShiPaint.setAntiAlias(true); mShiPaint.setStrokeWidth(2); } /*** 绘制十字光标* */privatevoidonDrawShiLine() { if(isShowShiLine){ StockBeanbean=stockList.get(0); floatmaxNum=Integer.MAX_VALUE; for (inta=0;a<stockList.size();a++){ StockBeanb=stockList.get(a); floatx=Math.abs(moveX-b.getStockX()); if(x<maxNum){ bean=b; maxNum=x; } } Log.i("onDrawShiLine",moveX+"----"+bean.getStockY()+"----"+bean.getStockX()); //绘制横线canvas.drawLine(rectLeft,bean.getStockY(),mWidth-rectRight,bean.getStockY(),mShiPaint); //绘制纵线canvas.drawLine(bean.getStockX(),rectTop,bean.getStockX(),mHeight-rectBottom,mShiPaint); //绘制右边价格边框RectrectR=newRect(); rectR.top=(int)bean.getStockY()-15; rectR.bottom=(int)bean.getStockY()+15; rectR.left=mWidth-46; rectR.right=mWidth-5; canvas.drawRect(rectR,mShiPaint); //绘制右边价格canvas.drawText(bean.getPrice()+"",mWidth-42,bean.getStockY()+5,mRectPaint); //绘制底部时间边框RectrectB=newRect(); rectB.top=mHeight-46; rectB.bottom=mHeight-10; rectB.left=(int) bean.getStockX()-35; rectB.right=(int) bean.getStockX()+40; canvas.drawRect(rectB,mShiPaint); //绘制底部时间canvas.drawText(bean.getTime()+"",bean.getStockX()-25,mHeight-25,mRectPaint); } } 补充说明,向View传递数据:/*** 获取价格中的最大和最小值* */privatefloatminStock,maxStock; privateString[] priceLeft=newString[5];//右边5个价格展示privateString[] bootomTime=newString[3];//底部3个时间展示publicvoidsetStockData(List<StockBean>list){ if(!list.isEmpty()){ isData=true; stockList=list; mRectPaintLodingText.setColor(0x00000000);//隐藏加载mRectPaintLoding.setColor(0x00000000);//隐藏加载floatminPrice=stockList.get(0).getPrice(); floatmaxPrice=stockList.get(0).getPrice(); for (inta=0;a<stockList.size();a++){ floatp=stockList.get(a).getPrice(); if(p<minPrice){ minPrice=p; } if(p>maxPrice){ maxPrice=p; } } bootomTime[0]=stockList.get(0).getTime(); bootomTime[1]=stockList.get(stockList.size()/2-1).getTime(); bootomTime[2]=stockList.get(stockList.size()-1).getTime(); minStock=minPrice; maxStock=maxPrice; floatp=(maxStock-minStock)/4; priceLeft[4]=minStock+""; priceLeft[3]=(minStock+p)+""; priceLeft[2]=(minStock+2*p)+""; priceLeft[1]=(minStock+3*p)+""; priceLeft[0]=maxStock+""; invalidate(); } } 加载蒙版展示(一开始先绘制加载,待数据传来时,隐藏加载,这里我隐藏的方法是,把画笔颜色设置为透明,如上述传递数据代码):/*** 初始化加载边框画笔信息* */privatePaintmRectPaintLoding; privatevoidinitRectPaintLoding(){ mRectPaintLoding=newPaint(); mRectPaintLoding.setColor(Color.parseColor("#ffffff")); mRectPaintLoding.setAntiAlias(true);//设置抗锯齿mRectPaintLoding.setStrokeWidth(2);//线条粗细mRectPaintLoding.setStyle(Paint.Style.FILL);//设置实心} /*** 初始化文字信息* */privatePaintmRectPaintLodingText; privatevoidinitRectPaintLodingText(){ mRectPaintLodingText=newPaint(); mRectPaintLodingText.setColor(Color.parseColor("#222222")); mRectPaintLodingText.setAntiAlias(true);//设置抗锯齿mRectPaintLodingText.setStrokeWidth(2);//线条粗细mRectPaintLodingText.setStyle(Paint.Style.STROKE);//设置空心mRectPaintLodingText.setTextSize(26); } /*** 绘制加载框* */privateStringrectLoding="正在加载……"; privatevoidonDrawRectLoding() { Rectrect=newRect(0,0,mWidth,mHeight); canvas.drawRect(rect,mRectPaintLoding); canvas.drawText(rectLoding,mWidth/2-mRectPaintLodingText.measureText(rectLoding) /2,mHeight/2,mRectPaintLodingText); }
至此,就绘制结束了,如有问题,可在下方评论提问,看到会及时回复。