在这篇博客中,将实现混合柱状图
在上一篇博客中讲到过,所有的图表类都只要重载
abstract protected void startContent(Canvas canvas);//绘制图表内容
abstract protected void drawXaxis(Canvas canvas);//绘制X轴
abstract protected void drawYaxis(Canvas canvas);//绘制Y轴
接下来就看看柱状图的这三个方法的具体实现
@Override
public void drawXaxis(Canvas canvas) {
XaxisData xaxisData=dataManager.getxData();//从管理器中取出描述X轴的实体类
int width=canvas.getWidth();
Paint paint=getPaint(xaxisData.getLineWidth(),xaxisData.getLineColor());
canvas.drawLine(originPoint.x,originPoint.y,width,originPoint.y,paint);//从原点开始绘制一条直线作为X轴
Paint textpaint=getPaint(3,xaxisData.getLineColor());
textpaint.setTextSize(xaxisData.getTextsize());
List<Integer> xvaluesRelaticeX=getXvaluesRelativeX(xaxisData,canvas,textpaint);//取得每个柱状图中心点的X坐标
//接下来是绘制每个柱状图在X轴上的描述,就是“1月”,“2月”。。。那些文字描述
Paint rectPaint=new Paint();
rectPaint.setStrokeJoin(Paint.Join.ROUND);
rectPaint.setTextSize(xaxisData.getTextsize());
for(int i=0;i<xvaluesRelaticeX.size();i++){
//先绘制一个圆角矩形,再绘制文字
textpaint.setColor(xaxisData.getXdataList().get(i).getColor());
rectPaint.setColor(xaxisData.getXdataList().get(i).getBackgroundColor());
// 矩形两边留出10,作为矩形的padding
int left=xvaluesRelaticeX.get(i)-10;
//头部偏移20是矩形和X轴之间留出空隙
int top=originPoint.y+20;
int right= (int) (xvaluesRelaticeX.get(i)+textpaint.measureText(xaxisData.getXdataList().get(i).getText())+10);
int bottom=top+xaxisData.getTextsize()+15;
canvas.drawRoundRect(new RectF(left,top,right,bottom),10,10,rectPaint);
//文字居中Y坐标计算参考http://blog.csdn.net/doctorzhong/article/details/53079511
int centerY= (int) ((top+bottom)/2-(textpaint.ascent()+textpaint.descent())/2);
canvas.drawText(xaxisData.getXdataList().get(i).getText(),xvaluesRelaticeX.get(i),centerY,textpaint);
}
}
Y轴的绘制函数
@Override
public void drawYaxis(Canvas canvas) {
int rangNum=dataManager.getRangeNum(); //获取Y轴要分成几段
int maxValue=dataManager.getMaxValues(); //Y轴能表示的最大数据
int yaxisLength=getYaxisLength(); //Y轴的长度
Paint yaxisLinePait=getPaint(dataManager.getYaxisLineWidth(),dataManager.getYaxisLineColor());
canvas.drawLine(originPoint.x,originPoint.y,originPoint.x,originPoint.y-yaxisLength-30,yaxisLinePait);//绘制Y轴
Paint paint=getPaint(20, Color.GRAY);
int rangeHeight=yaxisLength/rangNum; //计算Y轴分段后每一段的高度
int rangeValues=maxValue/rangNum; //计算每一段表示的数值大小
yaxisLinePait.setTextSize(dataManager.getYaxisTextsize());
yaxisLinePait.setColor(dataManager.getYaxisTextColor());
int xaxisLength=getXaxisLength(canvas);
Paint xuxianPaint=new Paint();//绘制虚线的画笔
xuxianPaint.setColor(Color.GRAY);
xuxianPaint.setStyle(Paint.Style.STROKE);//一定要设置这个属性,不然绘制不出虚线
for(int i=0;i<=rangNum;i++){
int yvalues=i*rangeValues;
int yaxis=i*rangeHeight;
int textwidth= (int) yaxisLinePait.measureText(yvalues+"");
int x=originPoint.x-textwidth-10;
int y=originPoint.y-yaxis;
canvas.drawText(yvalues+"",x,y,yaxisLinePait);
if(i!=0){
Path path = new Path();
path.moveTo(originPoint.x, y);
path.lineTo(originPoint.x+xaxisLength,y);
PathEffect effects = new DashPathEffect(new float[]{5,5},5);
xuxianPaint.setPathEffect(effects);
canvas.drawPath(path, xuxianPaint);
}
}
}
最后是绘制图表的内容,就是那些柱状矩形,提示的泡泡
@Override
public void startContent(Canvas canvas) {
allRectangel.clear();
List<YaxisData> ydataSet=dataManager.getyDataSet();//获取Y轴数据集合
List<Integer> xrelativeValue=getXvaluesRelativeCenterX(dataManager.getxData(),canvas);//获取每个柱状图中心点的坐标
for(int i=0;i<ydataSet.size();i++){
YaxisData yaxisData=ydataSet.get(i);
List<Integer> yrelativeValus=getYvaluesRelativeHeight(ydataSet.get(i)); //获取某个种类每个Y轴坐标对应的高度
Paint rectPaint=getPaint(5,yaxisData.getColor());
for(int j=0;j<xrelativeValue.size();j++){
int x=xrelativeValue.get(j);
int y=yrelativeValus.get(j);
int left=x-rectangleWidth/2;
int top=y;
int right=x+rectangleWidth/2;
int bottom=originPoint.y;
Rect r=new Rect(left,top,right,bottom);
canvas.drawRect(r,rectPaint);
if(i==0){
allRectangel.add(r);//把每个最高的矩形存起来,后面有大作用,我们公司的业务定好了第一层数据就是最大的,所以取i==0时的矩形就行
}
}
}
}
到这里这个柱状图就绘制完了,还差一步,点击某个柱状图的时候,矩形顶部出现该柱状图详情数据的泡泡
先看看触摸事件这里的处理
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
selectValues=drawUtil.getInformationByEventMotion(event);//获取触摸点的信息,selectValues封装了摸出到的矩形位置,该矩形的顶部坐标等信息,一会再详细介绍这个函数,如果触摸到矩形的话,selectValues就不为null,然后就是更新数据了,重新绘制View
if(selectValues!=null){
XaxisData xaxisData=chartDataManager.getxData();
int size=xaxisData.getXdataList().size();
for(int i=0;i<size;i++){
chartDataManager.getxData().getXdataList().get(i).setColor(Color.BLACK);
chartDataManager.getxData().getXdataList().get(i).setBackgroundColor(Color.WHITE);
}
chartDataManager.getxData().getXdataList().get(selectValues.getPosition()).setColor(Color.WHITE);
chartDataManager.getxData().getXdataList().get(selectValues.getPosition()).setBackgroundColor(Color.GREEN);
invalidate();
}
break;
}
return false;
}
这里再介绍一下getInformationByEventMotion(event)方法
public RectTouchInformation getInformationByEventMotion(MotionEvent event){
RectTouchInformation selectYvalues=new RectTouchInformation();
int position=0;
boolean flag=false;
//遍历上面绘制图表矩形时存起来的矩形集合,判断手指触摸的是哪个矩形
for(int i=0;i<allRectangel.size();i++){
Rect r=allRectangel.get(i);
if(r.contains((int)event.getX(),(int)event.getY())){
position=i;
flag=true;
break;
}
}
if(!flag){
return null;
}
//获取选中的每个柱状图的信息
selectYvalues.setPosition(position);
int size=dataManager.getyDataSet().size();
List<YaxisData> allYaxisData=dataManager.getyDataSet();
List<Yvalues> selectYpoint=new ArrayList<>();
for(int i=0;i<size;i++){
YaxisData yaxisData=allYaxisData.get(i);
Yvalues yvalues=yaxisData.getYdataList().get(position);
yvalues.setDescription(yaxisData.getDescription());
selectYpoint.add(yvalues);
}
//把选中矩形顶部中心的坐标封装进selectValues
Rect rect=allRectangel.get(position);
Point point=new Point(rect.left+rect.width()/2,rect.top);
selectYvalues.setRectTopLineCenter(point);
selectYvalues.setSelectValues(selectYpoint);
return selectYvalues;
}
接下来是看看如何把泡泡绘制出来的
public void drawSelectImage(Context context,Canvas canvas, RectTouchInformation selectRectInfor){
Point point=selectRectInfor.getRectTopLineCenter();
LinearLayout layout=new LinearLayout(context);
layout.setBackgroundColor(Color.WHITE);
layout.setOrientation(LinearLayout.VERTICAL);
int size=selectRectInfor.getSelectValues().size();
for(int i=0;i<size;i++){
Yvalues yvalues=selectRectInfor.getSelectValues().get(i);
TextView textView=new TextView(context);
textView.setText(yvalues.getDescription()+":"+yvalues.getValue());
LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textView.setPadding(10,2,10,2);
textView.setTextSize(10);
textView.setLayoutParams(params);
layout.addView(textView);
}
//泡泡的内容首先是把它展示在View上,然后从这个View上获取到bitmap,然后绘制这个Bitmap就行,如果还是使用绘制矩形和文字的方法来实在是太麻烦了,绘制一行一行的文字就愁死人了
Bitmap selectBitmap=convertViewToBitmap(layout);
int width=selectBitmap.getWidth();
int height=selectBitmap.getHeight();
int x=point.x-width/2;
int y=point.y-height-20;
Paint paint=new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(dataManager.getyDataSet().get(0).getColor());
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
RectF rectF=new RectF(x-2,y-2,x+width+2,y+height+2);
//这里是给泡泡设置偏移量,以防泡泡过长超出控件了
if(rectF.left<originPoint.x){
int offset= (int) (originPoint.x-rectF.left+30);
x+=offset;
rectF.left=rectF.left+offset;
rectF.right=rectF.right+offset;
}else if(rectF.right>canvas.getWidth()){
int offset= (int) (rectF.right-canvas.getWidth()+30);
x-=offset;
rectF.left=rectF.left-offset;
rectF.right=rectF.right-offset;
}
//最后是绘制出指示箭头
canvas.drawRoundRect(rectF,10,10,paint);
canvas.drawLine(point.x,point.y,point.x-10,rectF.bottom,paint);
canvas.drawLine(point.x,point.y,point.x+10,rectF.bottom,paint);
paint.setColor(Color.WHITE);
canvas.drawLine(point.x,rectF.bottom,point.x-10,rectF.bottom,paint);
canvas.drawLine(point.x,rectF.bottom,point.x+10,rectF.bottom,paint);
canvas.drawBitmap(selectBitmap,x,y,new Paint());
}